From b829a66281059839435fd59efe3aeed68844a78f Mon Sep 17 00:00:00 2001 From: Artem Araptanov Date: Fri, 28 Apr 2023 16:41:53 +0400 Subject: [PATCH] feature: get custom properties from xlsx --- .../ObjectPrintingTests/ExcelParsingTests.cs | 46 ++++++++++++++++++ .../Files/customProperties.xlsx | Bin 0 -> 7020 bytes .../Helpers/CustomFilePropertiesPartHelper.cs | 18 +++++++ .../Primitives/IExcelDocument.cs | 5 ++ .../Implementations/ExcelDocument.cs | 35 +++++++++++++ version.json | 2 +- 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 Excel.TemplateEngine.Tests/ObjectPrintingTests/Files/customProperties.xlsx create mode 100644 Excel.TemplateEngine/FileGenerating/Helpers/CustomFilePropertiesPartHelper.cs diff --git a/Excel.TemplateEngine.Tests/ObjectPrintingTests/ExcelParsingTests.cs b/Excel.TemplateEngine.Tests/ObjectPrintingTests/ExcelParsingTests.cs index 251c81b..d033ce6 100644 --- a/Excel.TemplateEngine.Tests/ObjectPrintingTests/ExcelParsingTests.cs +++ b/Excel.TemplateEngine.Tests/ObjectPrintingTests/ExcelParsingTests.cs @@ -8,6 +8,7 @@ using SkbKontur.Excel.TemplateEngine.Exceptions; using SkbKontur.Excel.TemplateEngine.FileGenerating; +using SkbKontur.Excel.TemplateEngine.FileGenerating.Primitives; using SkbKontur.Excel.TemplateEngine.ObjectPrinting.ExcelDocumentPrimitives.Implementations; using SkbKontur.Excel.TemplateEngine.ObjectPrinting.LazyParse; using SkbKontur.Excel.TemplateEngine.ObjectPrinting.NavigationPrimitives.Implementations; @@ -291,6 +292,51 @@ public void TestImportAfterCreate(string extension) model.Type.Should().Be("Значение 2"); } + [TestCase("TemplateVersion", "1.9.text.$$%")] + [TestCase("another custom property", "another value")] + [TestCase("non-existent key", null)] + [TestCase(null, null)] + [TestCase("", null)] + public void TestGetCustomProperty(string key, string value) + { + var template = File.ReadAllBytes(GetFilePath("customProperties.xlsx")); + using (var templateModel = ExcelDocumentFactory.CreateFromTemplate(template, logger)) + { + CheckCustomProperty(templateModel, key, value); + var printedDocument = templateModel.CloseAndGetDocumentBytes(); + using (var printedDocumentModel = ExcelDocumentFactory.CreateFromTemplate(printedDocument, logger)) + { + CheckCustomProperty(printedDocumentModel, key, value); + } + } + } + + [TestCase("customProperties.xlsx")] + [TestCase("empty.xlsx", Description = "template file without custom properties")] + public void TestSetCustomProperty(string fileName) + { + var addedKey = "NewKey"; + var addedValue = "CertainValue"; + var templateBytes = File.ReadAllBytes(GetFilePath(fileName)); + using (var template = ExcelDocumentFactory.CreateFromTemplate(templateBytes, logger)) + { + CheckCustomProperty(template, addedKey, null); + template.SetCustomProperty(addedKey, addedValue); + CheckCustomProperty(template, addedKey, addedValue); + var printedDocumentBytes = template.CloseAndGetDocumentBytes(); + using (var printedDocument = ExcelDocumentFactory.CreateFromTemplate(printedDocumentBytes, logger)) + { + CheckCustomProperty(printedDocument, addedKey, addedValue); + } + } + } + + private void CheckCustomProperty(IExcelDocument documentModel, string key, string value) + { + documentModel.TryGetCustomProperty(key, out var printedVersion).Should().Be(value != null); + printedVersion.Should().Be(value); + } + private (TModel model, Dictionary mappingForErrors) Parse(string templateFileName, string targetFileName) where TModel : new() { diff --git a/Excel.TemplateEngine.Tests/ObjectPrintingTests/Files/customProperties.xlsx b/Excel.TemplateEngine.Tests/ObjectPrintingTests/Files/customProperties.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7cf1f6c250a5fa45e068cd3a6561efbea4e19b21 GIT binary patch literal 7020 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 + .FirstOrDefault(i => i?.Name?.Value == key); + } +} \ No newline at end of file diff --git a/Excel.TemplateEngine/FileGenerating/Primitives/IExcelDocument.cs b/Excel.TemplateEngine/FileGenerating/Primitives/IExcelDocument.cs index ecd7514..b06e11c 100644 --- a/Excel.TemplateEngine/FileGenerating/Primitives/IExcelDocument.cs +++ b/Excel.TemplateEngine/FileGenerating/Primitives/IExcelDocument.cs @@ -34,5 +34,10 @@ public interface IExcelDocument : IDisposable void AddDescription([NotNull] string text); void CopyVbaInfoFrom([NotNull] IExcelDocument excelDocument); + + [ContractAnnotation("=> true, value:notnull; => false, value:null")] + bool TryGetCustomProperty([NotNull] string key, out string value); + + void SetCustomProperty([NotNull] string key, string value); } } \ No newline at end of file diff --git a/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelDocument.cs b/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelDocument.cs index a008bd1..3906f15 100644 --- a/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelDocument.cs +++ b/Excel.TemplateEngine/FileGenerating/Primitives/Implementations/ExcelDocument.cs @@ -5,9 +5,11 @@ using System.Text; using System.Xml; +using DocumentFormat.OpenXml.CustomProperties; using DocumentFormat.OpenXml.Drawing; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; +using DocumentFormat.OpenXml.VariantTypes; using JetBrains.Annotations; @@ -168,6 +170,39 @@ public void RenameWorksheet(int index, [NotNull] string name) spreadsheetDocument.WorkbookPart.Workbook.Sheets.Elements().ElementAt(index).Name = name; } + public bool TryGetCustomProperty(string key, out string value) + { + ThrowIfSpreadsheetDisposed(); + var property = spreadsheetDocument.CustomFilePropertiesPart?.GetProperty(key); + value = property?.InnerText; + return value != null; + } + + public void SetCustomProperty(string key, string value) + { + // https://learn.microsoft.com/en-us/office/open-xml/how-to-set-a-custom-property-in-a-word-processing-document + const string customPropertyFormatId = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}"; + var customProps = spreadsheetDocument.CustomFilePropertiesPart + ?? spreadsheetDocument.AddCustomFilePropertiesPart(); + // ReSharper disable once ConstantNullCoalescingCondition + customProps.Properties ??= new Properties(); + + var existentProperty = customProps.GetProperty(key); + existentProperty?.Remove(); + customProps.Properties.AppendChild(new CustomDocumentProperty + { + VTLPWSTR = new VTLPWSTR(value), + FormatId = customPropertyFormatId, + Name = key + }); + var pid = 2; + foreach (var item in customProps.Properties.OfType()) + { + item.PropertyId = pid++; + } + customProps.Properties.Save(); + } + [NotNull] public IExcelWorksheet AddWorksheet([NotNull] string worksheetName) { diff --git a/version.json b/version.json index 1ebd4c3..abe9271 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.2", + "version": "1.3", "assemblyVersion": { "precision": "build" },