From 9619cdd6d426fce3b256611eef27e245b02be727 Mon Sep 17 00:00:00 2001 From: Alexander Dobrynin Date: Wed, 5 Aug 2020 18:08:39 +0300 Subject: [PATCH 1/2] Update DocumentFormat.OpenXml version 2.9.1 => 2.11.3 --- Excel.TemplateEngine/Excel.TemplateEngine.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Excel.TemplateEngine/Excel.TemplateEngine.csproj b/Excel.TemplateEngine/Excel.TemplateEngine.csproj index bfefe8f..26af842 100644 --- a/Excel.TemplateEngine/Excel.TemplateEngine.csproj +++ b/Excel.TemplateEngine/Excel.TemplateEngine.csproj @@ -10,7 +10,7 @@ - + From cdf426e276369c26022e4ce1c802240589cbcbb6 Mon Sep 17 00:00:00 2001 From: Alexander Dobrynin Date: Wed, 5 Aug 2020 18:46:58 +0300 Subject: [PATCH 2/2] Insert worksheet child nodes according to excel's schema --- .../ManualPrintingTests.cs | 26 ++++--- .../Helpers/OpenXmlElementHelper.cs | 76 +++++++++++++++++++ .../Implementations/ExcelWorksheet.cs | 58 +++----------- 3 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 Excel.TemplateEngine/FileGenerating/Helpers/OpenXmlElementHelper.cs diff --git a/Excel.TemplateEngine.Tests/ObjectPrintingTests/ManualPrintingTests.cs b/Excel.TemplateEngine.Tests/ObjectPrintingTests/ManualPrintingTests.cs index 4bdc53c..892404c 100644 --- a/Excel.TemplateEngine.Tests/ObjectPrintingTests/ManualPrintingTests.cs +++ b/Excel.TemplateEngine.Tests/ObjectPrintingTests/ManualPrintingTests.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using SkbKontur.Excel.TemplateEngine.FileGenerating; +using SkbKontur.Excel.TemplateEngine.FileGenerating.DataTypes; using SkbKontur.Excel.TemplateEngine.FileGenerating.Primitives; using SkbKontur.Excel.TemplateEngine.ObjectPrinting.ExcelDocumentPrimitives.Implementations; using SkbKontur.Excel.TemplateEngine.ObjectPrinting.NavigationPrimitives.Implementations; @@ -26,15 +27,7 @@ public void TestPrintingDropDownFromTheOtherWorksheet() { targetDocument.CopyVbaInfoFrom(templateDocument); - foreach (var index in Enumerable.Range(1, templateDocument.GetWorksheetCount() - 1)) - { - var worksheet = templateDocument.GetWorksheet(index); - var name = templateDocument.GetWorksheetName(index); - var innerTemplateEngine = new SkbKontur.Excel.TemplateEngine.TemplateEngine(new ExcelTable(worksheet), logger); - var targetWorksheet = targetDocument.AddWorksheet(name); - var innerTableBuilder = new TableBuilder(new ExcelTable(targetWorksheet), new TableNavigator(new CellPosition("A1"), logger)); - innerTemplateEngine.Render(innerTableBuilder, new {}); - } + CopySecondaryWorksheets(templateDocument, targetDocument); var template = new ExcelTable(templateDocument.GetWorksheet(0)); var templateEngine = new SkbKontur.Excel.TemplateEngine.TemplateEngine(template, logger); @@ -247,6 +240,7 @@ public void TestPrintingCommentsWithEnabledDataValidationByOtherSheetData() var target = new ExcelTable(targetDocument.GetWorksheet(0)); var tableNavigator = new TableNavigator(new CellPosition("A1"), logger); + targetDocument.GetWorksheet(0).SetPrinterSettings(printerSettings); var tableBuilder = new TableBuilder(target, tableNavigator, new Style(template.GetCell(new CellPosition("A1")))); templateEngine.Render(tableBuilder, new {}); @@ -276,5 +270,19 @@ private void CopySecondaryWorksheets(IExcelDocument templateDocument, IExcelDocu } private readonly ConsoleLog logger = new ConsoleLog(); + + private static readonly ExcelPrinterSettings printerSettings = new ExcelPrinterSettings + { + PrintingOrientation = ExcelPrintingOrientation.Landscape, + PageMargins = new ExcelPageMargins + { + Left = 0.25, + Right = 0.25, + Top = 0.75, + Bottom = 0.75, + Header = 0.3, + Footer = 0.3 + } + }; } } \ No newline at end of file diff --git a/Excel.TemplateEngine/FileGenerating/Helpers/OpenXmlElementHelper.cs b/Excel.TemplateEngine/FileGenerating/Helpers/OpenXmlElementHelper.cs new file mode 100644 index 0000000..9de02d8 --- /dev/null +++ b/Excel.TemplateEngine/FileGenerating/Helpers/OpenXmlElementHelper.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; + +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Spreadsheet; + +namespace SkbKontur.Excel.TemplateEngine.FileGenerating.Helpers +{ + public static class OpenXmlElementHelper + { + // todo (a.dobrynin, 05.08.2020): replace this method with targetWorksheet.AddChild(child) when DocumentFormat.OpenXml 2.12.0 is released + // https://github.com/OfficeDev/Open-XML-SDK/pull/774 + /// + /// Adds node in the correct location according to the schema + /// http://msdn.microsoft.com/en-us/library/office/cc880096(v=office.15).aspx + /// https://docs.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.worksheet?view=openxml-2.8.1#definition + /// https://github.com/OfficeDev/Open-XML-SDK/blob/058ec42001ca97850fd82cc16e3b234c155a6e7e/src/DocumentFormat.OpenXml/GeneratedCode/schemas_microsoft_com_office_excel_2006_main.g.cs#L90 + /// + public static T AddChildToCorrectLocation(this OpenXmlCompositeElement parent, T child) where T : OpenXmlElement + { + if (!(parent is Worksheet)) + throw new InvalidOperationException("Schema-oriented insertion is supported only for Worksheets"); + + var precedingElementTypes = openXmlWorksheetNodesOrder.TakeWhile(x => x != typeof(T)); + var closestPrecedingSibling = precedingElementTypes.Reverse() + .Select(precedingElementType => parent.Elements().LastOrDefault(x => x.GetType() == precedingElementType)) + .FirstOrDefault(existingElement => existingElement != null); + + return closestPrecedingSibling != null + ? parent.InsertAfter(child, closestPrecedingSibling) + : parent.InsertAt(child, 0); + } + + private static readonly Type[] openXmlWorksheetNodesOrder = + { + typeof(SheetProperties), + typeof(SheetDimension), + typeof(SheetViews), + typeof(SheetFormatProperties), + typeof(Columns), + typeof(SheetData), + typeof(SheetCalculationProperties), + typeof(SheetProtection), + typeof(ProtectedRanges), + typeof(Scenarios), + typeof(AutoFilter), + typeof(SortState), + typeof(DataConsolidate), + typeof(CustomSheetViews), + typeof(MergeCells), + typeof(PhoneticProperties), + typeof(ConditionalFormatting), + typeof(DataValidations), + typeof(Hyperlinks), + typeof(PrintOptions), + typeof(PageMargins), + typeof(PageSetup), + typeof(HeaderFooter), + typeof(RowBreaks), + typeof(ColumnBreaks), + typeof(CustomProperties), + typeof(CellWatches), + typeof(IgnoredErrors), + typeof(Drawing), + typeof(LegacyDrawing), + typeof(LegacyDrawingHeaderFooter), + typeof(DrawingHeaderFooter), + typeof(Picture), + typeof(OleObjects), + typeof(Controls), + typeof(WebPublishItems), + typeof(TableParts), + typeof(WorksheetExtensionList), + }; + } +} \ No newline at end of file diff --git a/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelWorksheet.cs b/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelWorksheet.cs index 71dc8f5..735a3e6 100644 --- a/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelWorksheet.cs +++ b/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelWorksheet.cs @@ -15,6 +15,7 @@ using SkbKontur.Excel.TemplateEngine.FileGenerating.Caches; using SkbKontur.Excel.TemplateEngine.FileGenerating.DataTypes; +using SkbKontur.Excel.TemplateEngine.FileGenerating.Helpers; using Vostok.Logging.Abstractions; @@ -50,25 +51,25 @@ public void SetPrinterSettings(ExcelPrinterSettings excelPrinterSettings) pageMargins.Footer = excelPrinterSettings.PageMargins.Footer; if (!worksheet.Elements().Any()) - worksheet.AppendChild(pageMargins); + InsertWorksheetChild(worksheet, pageMargins); } var pageSetup = worksheet.Elements().FirstOrDefault() ?? new PageSetup(); pageSetup.Orientation = excelPrinterSettings.PrintingOrientation == ExcelPrintingOrientation.Landscape ? OrientationValues.Landscape : OrientationValues.Portrait; if (!worksheet.Elements().Any()) - worksheet.AppendChild(pageSetup); + InsertWorksheetChild(worksheet, pageSetup); } public void MergeCells(ExcelCellIndex upperLeft, ExcelCellIndex lowerRight) { - var mergeCells = worksheet.GetFirstChild() ?? CreateMergeCellsWorksheetPart(); + var mergeCells = worksheet.GetFirstChild() ?? InsertWorksheetChild(worksheet, new MergeCells()); mergeCells.AppendChild(new MergeCell {Reference = $"{upperLeft.CellReference}:{lowerRight.CellReference}"}); } public void ResizeColumn(int columnIndex, double width) { - var columns = worksheet.GetFirstChild() ?? CreateColumns(); + var columns = worksheet.GetFirstChild() ?? InsertWorksheetChild(worksheet, new Columns()); while (columns.ChildElements.Count < columnIndex) { columns.AppendChild(new Column @@ -142,7 +143,7 @@ public void CopyDataValidationsFrom([NotNull] IExcelWorksheet template) var dataValidations = templateWorksheet.GetFirstChild(); if (dataValidations == null) return; - worksheet.InsertBefore(dataValidations.CloneNode(true), worksheet.GetFirstChild()); + InsertWorksheetChild(worksheet, (DataValidations)dataValidations.CloneNode(true)); } public void CopyWorksheetExtensionListFrom(IExcelWorksheet template) @@ -246,7 +247,7 @@ private void CopyDrawingsPartAndGetId([NotNull] WorksheetPart templateWorksheetP var drawingsPartId = templateWorksheetPart.DrawingsPart == null ? null : templateWorksheetPart.GetIdOfPart(templateWorksheetPart.DrawingsPart); SafelyAddPart(targetWorksheet.WorksheetPart, drawingsPart, drawingsPartId); targetWorksheet.RemoveAllChildren(); - targetWorksheet.Append(new Drawing {Id = drawingsPartId}); + InsertWorksheetChild(targetWorksheet, new Drawing {Id = drawingsPartId}); } [SuppressMessage("ReSharper", "PossiblyMistakenUseOfParamsMethod")] @@ -259,7 +260,7 @@ private void CopyVmlDrawingPartAndGetId([NotNull] WorksheetPart templateWorkshee var vmlDrawingPartId = vmlDrawingPart == null ? null : templateWorksheetPart.GetIdOfPart(vmlDrawingPart); SafelyAddPart(targetWorksheet.WorksheetPart, vmlDrawingPart, vmlDrawingPartId); targetWorksheet.RemoveAllChildren(); - targetWorksheet.InsertAfter(new LegacyDrawing {Id = vmlDrawingPartId}, worksheet.GetFirstChild()); + InsertWorksheetChild(targetWorksheet, new LegacyDrawing {Id = vmlDrawingPartId}); } [CanBeNull] @@ -346,47 +347,9 @@ public IExcelRow CreateRow(int rowIndex) return new ExcelRow(newRow, documentStyle, excelSharedStrings); } - private MergeCells CreateMergeCellsWorksheetPart() + private static T InsertWorksheetChild(Worksheet openXmlWorksheetNodesOrder, T child) where T : OpenXmlElement { - // Имеет принципиальное значение, куда именно вставлять элемент MergeCells - // см. http://msdn.microsoft.com/en-us/library/office/cc880096(v=office.15).aspx - - var mergeCells = new MergeCells(); - if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - else - worksheet.InsertAfter(mergeCells, worksheet.Elements().First()); - return mergeCells; - } - - private Columns CreateColumns() - { - var columns = new Columns(); - if (worksheet.Elements().Any()) - worksheet.InsertAfter(columns, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(columns, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(columns, worksheet.Elements().First()); - else if (worksheet.Elements().Any()) - worksheet.InsertAfter(columns, worksheet.Elements().First()); - else - worksheet.InsertAt(columns, 0); - return columns; + return openXmlWorksheetNodesOrder.AddChildToCorrectLocation(child); } public IExcelDocument ExcelDocument { get; } @@ -396,5 +359,6 @@ private Columns CreateColumns() private readonly ILog logger; private readonly Worksheet worksheet; private readonly TreeDictionary rowsCache; + } } \ No newline at end of file