From 2239f374081acc6cf8a3c451cdcea58efb82b441 Mon Sep 17 00:00:00 2001 From: Andy Howard Date: Tue, 27 Feb 2024 15:45:02 +0000 Subject: [PATCH] Saxonite 1.2.11.2 --- ReadMe.md | 7 +- SaCommonVS.sln | 10 +- SacsCoreUnitTests/SacsCoreUnitTests.csproj | 2 +- .../Base/Utils/SerializableUtil_Tester.cs | 66 + .../Standard/Base/Utils/SysUtil_Tester.cs | 111 + SacsCoreWeb/SacsCoreWeb.csproj | 26 +- SacsCoreWeb/Web/Components/SaController.cs | 423 ++++ SacsCoreWeb/Web/Components/SaModel.cs | 150 ++ .../SacsStandardAccounts.csproj | 2 +- SacsStandardBase/Base/Core/ColumnDfn.cs | 5 +- SacsStandardBase/Base/Core/DataValue.cs | 53 + SacsStandardBase/Base/Core/SacsDisposable.cs | 2 +- .../Base/Helpers/SacsContentTypeHelper.cs | 32 +- SacsStandardBase/Base/Procs/SaProcess_Base.cs | 400 +++- SacsStandardBase/Base/Tasks/SacsTaskData.cs | 10 +- SacsStandardBase/Base/Utils/ImageUtils.cs | 5 +- .../Base/Utils/SerializableUtil.cs | 33 +- SacsStandardBase/Base/Utils/StringUtil.cs | 14 + SacsStandardBase/Base/Utils/SysUtil.cs | 79 +- SacsStandardBase/SacsStandardBase.csproj | 8 +- .../Dbas/Base/SaDbDfnOdbcDriver.cs | 10 +- SacsStandardDbas/Dbas/Base/SacsDatabase.cs | 70 +- .../Dbas/CodeTypes/Internal/SaCodeType_Db.cs | 1 - .../Dbas/CodeTypes/Internal/SaCode_Db.cs | 1 - .../Dbas/Data/SacsDatabaseOdbc.cs | 34 +- .../Internal/SaInterfaceDataItem_Db.cs | 1 - .../Internal/SaInterfaceProcessDataItem_Db.cs | 1 - .../Internal/SaInterfaceProcessMessage_Db.cs | 1 - .../Internal/SaInterfaceProcess_Db.cs | 1 - .../Interfaces/Internal/SaInterface_Db.cs | 1 - .../Dbas/ItemDocs/Internal/SaItemDoc_Db.cs | 1 - .../ItemOptions/Internal/SaItemOption_Db.cs | 1 - .../SaDbProcess_CompareDatabaseSchemas.cs | 25 +- .../Proc/SaDbProcess_ConvertDbDefinition.cs | 93 +- .../Dbas/Proc/SaDbProcess_ConvertDbSchema.cs | 4 +- .../ReferenceData/Internal/Language_Db.cs | 1 - .../Dbas/ReferenceData/Internal/Region_Db.cs | 1 - .../Dbas/ReferenceData/Internal/TaxCode_Db.cs | 1 - .../ReferenceData/Internal/UnitMeasure_Db.cs | 1 - .../Dbas/Security/Internal/SaSecItem_Db.cs | 1 - .../Dbas/Security/Internal/SaSecLink_Db.cs | 1 - .../UpdateLog/Internal/SaAttrUpdate_Db.cs | 1 - SacsStandardDbas/SacsStandardDbas.csproj | 4 +- .../Mobile/Forms/Layouts/SaxGrid.cs | 1 + .../Mobile/Forms/Panels/SaxPanel.cs | 1 + SacsStandardMob/SacsStandardMob.csproj | 6 +- SacsStandardProc/Proc/Excel/ExcelHelper.cs | 38 + .../Proc/Excel/SaMsSpreadsheet-Dfn.cs | 4 +- .../Proc/Excel/SaMsSpreadsheet.cs | 448 ++-- .../Proc/Excel/SaMsSpreadsheet_Direct.cs | 375 ++-- .../Proc/Excel/SaMsSpreadsheet_OpenXml.cs | 213 +- .../Proc/Excel/SaMsSpreadsheet_POI.cs | 1988 ++++++++++++----- SacsStandardProc/Proc/Procs/SaProcess.cs | 16 - SacsStandardProc/SacsStandardProc.csproj | 4 +- 54 files changed, 3613 insertions(+), 1175 deletions(-) create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/SerializableUtil_Tester.cs create mode 100644 SacsCoreUnitTests/Standard/Base/Utils/SysUtil_Tester.cs create mode 100644 SacsCoreWeb/Web/Components/SaController.cs create mode 100644 SacsCoreWeb/Web/Components/SaModel.cs create mode 100644 SacsStandardProc/Proc/Excel/ExcelHelper.cs diff --git a/ReadMe.md b/ReadMe.md index c4fd94c..54720a4 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,9 +1,10 @@ # Sarand -## Common Projects Visual Studio 2019 +## Common Projects Visual Studio 2022 ### Modules +* NB All projects upgraded to .Net 6, 28Nov23. * SACS Standard Base * Base class library. .Net Standard 2.1. C#. * SACS Standard Dbas @@ -13,7 +14,7 @@ * SACS Standard Mob * Base Mobile application library. .Net Standard 2.1. C#. * SACS Core Web - * Base Web application library. .Net Core 3.1. C#. + * Base Web application library. [C# .Net 6] * SACS Core Unit Tests - * Unit Tests. .Net Core 3.1. C#. + * Unit Tests. [C# .Net 6] diff --git a/SaCommonVS.sln b/SaCommonVS.sln index 90d5ef7..b611761 100644 --- a/SaCommonVS.sln +++ b/SaCommonVS.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31112.23 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34316.72 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardBase", "SacsStandardBase\SacsStandardBase.csproj", "{3A8EE4B3-2335-420B-9619-D1622AF511FA}" EndProject @@ -20,6 +20,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution files", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardAccounts", "SacsStandardAccounts\SacsStandardAccounts.csproj", "{C62999E2-1947-4EB7-82D2-A0EDB67D2F10}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SacsStandardProc", "SacsStandardProc\SacsStandardProc.csproj", "{3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +52,10 @@ Global {C62999E2-1947-4EB7-82D2-A0EDB67D2F10}.Debug|Any CPU.Build.0 = Debug|Any CPU {C62999E2-1947-4EB7-82D2-A0EDB67D2F10}.Release|Any CPU.ActiveCfg = Release|Any CPU {C62999E2-1947-4EB7-82D2-A0EDB67D2F10}.Release|Any CPU.Build.0 = Release|Any CPU + {3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3988EE9F-FFCD-481A-AF5E-AA7E5DB5A415}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SacsCoreUnitTests/SacsCoreUnitTests.csproj b/SacsCoreUnitTests/SacsCoreUnitTests.csproj index a471e94..68a8b4a 100644 --- a/SacsCoreUnitTests/SacsCoreUnitTests.csproj +++ b/SacsCoreUnitTests/SacsCoreUnitTests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 false diff --git a/SacsCoreUnitTests/Standard/Base/Utils/SerializableUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/SerializableUtil_Tester.cs new file mode 100644 index 0000000..158c0e1 --- /dev/null +++ b/SacsCoreUnitTests/Standard/Base/Utils/SerializableUtil_Tester.cs @@ -0,0 +1,66 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sacs.UnitTests.Net.Standard.Base.Utils +{ + [TestClass] + public class SerializableUtil_Tester + { + //------------------------------------------------------------------- + // Definitions. + [Serializable] + private class TestData + { + public String stringValue = "String Value"; + public long longValue = 123456; + } + [Serializable] + private class TestDataA : TestData + { + public TestDataA() + { + stringValue = "String Value A"; + longValue = 654321; + } + } + + //------------------------------------------------------------------- + // Data members. + private Random random = new Random(); + + //------------------------------------------------------------------- + // Deep Copy. + //------------------------------------------------------------------- + [TestMethod] + public void SerializableUtil_DeepCopy() + { + // Clone test data. + TestData data = new TestData(); + TestData data_DeepCopy = SerializableUtil.DeepCopy(data); + + // Verify results. + Assert.AreEqual(true, data_DeepCopy is TestData); + Assert.AreEqual(data.stringValue, data_DeepCopy.stringValue); + Assert.AreEqual(data.longValue, data_DeepCopy.longValue); + + // Clone test data A. + TestDataA dataA = new TestDataA(); + TestDataA dataA_DeepCopy = SerializableUtil.DeepCopy(dataA); + + // Verify results. + Assert.AreEqual(true, dataA_DeepCopy is TestData); + Assert.AreEqual(true, dataA_DeepCopy is TestDataA); + Assert.AreEqual(dataA.stringValue, dataA_DeepCopy.stringValue); + Assert.AreEqual(dataA.longValue, dataA_DeepCopy.longValue); + Assert.AreNotEqual(data.stringValue, dataA_DeepCopy.stringValue); + Assert.AreNotEqual(data.longValue, dataA_DeepCopy.longValue); + } + + //------------------------------------------------------------------- + } +} diff --git a/SacsCoreUnitTests/Standard/Base/Utils/SysUtil_Tester.cs b/SacsCoreUnitTests/Standard/Base/Utils/SysUtil_Tester.cs new file mode 100644 index 0000000..1ce66e9 --- /dev/null +++ b/SacsCoreUnitTests/Standard/Base/Utils/SysUtil_Tester.cs @@ -0,0 +1,111 @@ +using System; +using System.Diagnostics; +using System.Reflection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Sarand.Net.Standard.Base.Utils; + +namespace Sarand.UnitTests.Core.Base.Utils +{ + [TestClass] + public class SysUtil_Tester + { + //------------------------------------------------------------------- + // Data members. + private static readonly String ClassName_GeneratedMemberName_Prefix = "<"; + private static readonly String ClassName_CompilerGeneratedClass = ClassName_GeneratedMemberName_Prefix + ">c"; + + //------------------------------------------------------------------- + // Calling code class name. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_GetCallerClassName_Test() + { + // Leading spaces. + Assert.AreNotEqual(null, SysUtil.GetCallerClassName()); + Assert.AreNotEqual("", SysUtil.GetCallerClassName()); + Assert.AreEqual("SysUtil_Tester", SysUtil.GetCallerClassName()); + + // Compiler generated context. + Func temp = () => { return SysUtil.GetCallerClassName(); }; + Assert.AreEqual(ClassName_CompilerGeneratedClass, temp()); + + // Anonymous function. + Assert.AreEqual(ClassName_CompilerGeneratedClass, ((Func)(() => SysUtil.GetCallerClassName()))()); + Assert.AreEqual(ClassName_CompilerGeneratedClass, new Func(() => SysUtil.GetCallerClassName())()); + } + + //------------------------------------------------------------------- + // Calling code function name. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_GetCallerMemberName_Test() + { + // Leading spaces. + Assert.AreNotEqual(null, SysUtil.GetCallerMemberName()); + Assert.AreNotEqual("", SysUtil.GetCallerMemberName()); + Assert.AreEqual("SysUtil_GetCallerMemberName_Test", SysUtil.GetCallerMemberName()); + Assert.AreEqual(nameof(SysUtil_GetCallerMemberName_Test), SysUtil.GetCallerMemberName()); + Assert.AreEqual(MethodBase.GetCurrentMethod().Name, SysUtil.GetCallerMemberName()); + + // Compiler generated context. + Func temp = () => { return SysUtil.GetCallerMemberName(); }; + Assert.AreEqual(MethodBase.GetCurrentMethod().Name, temp()); + + // Anonymous function. + Assert.AreEqual(MethodBase.GetCurrentMethod().Name, ((Func)(() => SysUtil.GetCallerMemberName()))()); + Assert.AreEqual(MethodBase.GetCurrentMethod().Name, new Func(() => SysUtil.GetCallerMemberName())()); + } + + //------------------------------------------------------------------- + // Calling code file path. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_GetCallerFilePath_Test() + { + // Leading spaces. + Assert.AreNotEqual(null, SysUtil.GetCallerFilePath()); + Assert.AreNotEqual("", SysUtil.GetCallerFilePath()); + Assert.AreEqual(@"D:\Sarand\Projects\Users\AHoward\VS2022\Common-VS\Workspace\SacsCoreUnitTests\Standard\Base\Utils\SysUtil_Tester.cs", SysUtil.GetCallerFilePath()); + Assert.AreEqual(true, SysUtil.GetCallerFilePath().EndsWith("SysUtil_Tester.cs")); + } + + //------------------------------------------------------------------- + // Calling code line number. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_GetCallerLineNumber_Test() + { + // Leading spaces. + Assert.AreNotEqual(0, SysUtil.GetCallerLineNumber()); + Assert.AreEqual((new StackFrame(0, true)).GetFileLineNumber(), SysUtil.GetCallerLineNumber()); + } + + //------------------------------------------------------------------- + // Calling code file path. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_GetCallerClassMethod_Test() + { + // Leading spaces. + Assert.AreNotEqual(null, SysUtil.GetCallerClassMethod()); + Assert.AreNotEqual("", SysUtil.GetCallerClassMethod()); + Assert.AreEqual(GetType().Name + "." + MethodBase.GetCurrentMethod().Name, SysUtil.GetCallerClassMethod()); + Assert.AreEqual(nameof(SysUtil_Tester) + "." + nameof(SysUtil_GetCallerClassMethod_Test), SysUtil.GetCallerClassMethod()); + } + + //------------------------------------------------------------------- + // Calling code file path. + //------------------------------------------------------------------- + [TestMethod] + public void SysUtil_Fn_Test() + { + // Leading spaces. + Assert.AreNotEqual(null, SysUtil.Fn); + Assert.AreNotEqual("", SysUtil.Fn); + Assert.AreEqual(GetType().Name + "." + MethodBase.GetCurrentMethod().Name, SysUtil.Fn); + Assert.AreEqual(nameof(SysUtil_Tester) + "." + nameof(SysUtil_Fn_Test), SysUtil.Fn); + } + + //------------------------------------------------------------------- + } +} diff --git a/SacsCoreWeb/SacsCoreWeb.csproj b/SacsCoreWeb/SacsCoreWeb.csproj index e3c7980..d1418d1 100644 --- a/SacsCoreWeb/SacsCoreWeb.csproj +++ b/SacsCoreWeb/SacsCoreWeb.csproj @@ -1,16 +1,20 @@ - + - - netcoreapp3.1 - Sacs.Net.Core - + + net6.0 + Sacs + - - - + + + - - - + + + + + + + diff --git a/SacsCoreWeb/Web/Components/SaController.cs b/SacsCoreWeb/Web/Components/SaController.cs new file mode 100644 index 0000000..f4bf7c8 --- /dev/null +++ b/SacsCoreWeb/Web/Components/SaController.cs @@ -0,0 +1,423 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Helpers; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using System.Web; + +namespace Sacs.Web.Components +{ + //======================================================================= + public class SaController : Controller + { + //------------------------------------------------------------------- + /// + /// Protected construction - use derived objects. + /// + protected SaController() + { + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + } + base.Dispose(disposing); + } + + //------------------------------------------------------------------- + // Properties. + private static String ClassName { get; } = nameof(SaController); + public DateTime CreatedAt { get; } = DateTime.Now; + public bool DebugMode { get; } = SacsSystem.DebugMode; + /// + /// Indicate whether session data should be stored by action. + /// Any action that might modify the session data must store it. + /// Some actions such as scoring a password's complexity don't need to. + /// + public virtual bool StoreSessionData { get; protected set; } = true; + + //------------------------------------------------------------------- + // Culture. + private CultureInfo sessionCulture; + private CultureInfo RequestCulture { get { return Request.HttpContext.Features.Get().RequestCulture.Culture ?? CultureInfo.InvariantCulture; } } + public CultureInfo SessionCulture + { + get + { + if (sessionCulture == null) + { + sessionCulture = RequestCulture; + // ToDo Adjust culture if needed. + } + return sessionCulture; + } + } + private string LanguageIso2 + { + get + { + var feature = HttpContext.Features.Get(); + return feature.RequestCulture.UICulture.TwoLetterISOLanguageName.ToLower(); + } + } + + //------------------------------------------------------------------- + // Redirection locations. + //------------------------------------------------------------------- + public static readonly String ParamName_SessionExpired = "SessionExpired"; + public enum RedirectLocation + { + WebAppBase, + WebAppHome, + WebAppHome_SessionExpired, + } + public String GetRedirectLocation(RedirectLocation redirectLocation) + { + string protocol = Request.Scheme; + var parameters = new RouteValueDictionary(); + String homeAction = Url.Action("Index", "Home", parameters, protocol); + switch (redirectLocation) + { + case RedirectLocation.WebAppBase: + return Regex.Replace(homeAction, "\\/$", "") + "/"; + case RedirectLocation.WebAppHome: + return homeAction; + case RedirectLocation.WebAppHome_SessionExpired: + // Set the session expired parameter so that the page can report it. + // NB Paramater must have a value to be included in the URL query string. + parameters.Add(ParamName_SessionExpired, "1"); + return homeAction; + } + return ""; + } + + //------------------------------------------------------------------- + // Action execution events. + //------------------------------------------------------------------- + public override void OnActionExecuting(ActionExecutingContext filterContext) + { + //if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuting" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); + + base.OnActionExecuting(filterContext); + } + public override void OnActionExecuted(ActionExecutedContext filterContext) + { + //if (DebugMode) SaLogger.Debug(GetType().Name + ".OnActionExecuted" + (ViewData.Model != null ? " Model: " + ViewData.Model : "")); + + base.OnActionExecuted(filterContext); + } + + //------------------------------------------------------------------- + public static String EncryptParameters(NameValueCollection parameters, String base64IV, String base64Key) + { + String fn = ClassName + ".EncryptParameters"; + try + { + // Copy the list of parameters and sort the keys. + NameValueCollection parametersToEncrypt = new NameValueCollection(parameters); + String[] paramKeys = parametersToEncrypt.AllKeys; + Array.Sort(paramKeys); + + // Serialise parameters as if in a URL query string - so that they can be deserialised in DecryptParameters() below. + // Start with a random length parameter name at start of list to obscure the encryption. + string queryString = SacsRandomHelper.getRandomLengthNumber(4) + "&"; + queryString += String.Join("&", paramKeys.Select(a => a + "=" + HttpUtility.UrlEncode(parametersToEncrypt[a]))); + + // Encrypt data. + String encrypted = SacsEncryptionHelper.Encrypt(queryString, base64IV, base64Key); + //if (SacsSystem.DebugMode) SaLogger.Debug(fn + "\n\tData: " + queryString + " \n\tEncrypted: " + encrypted); + return encrypted; + } + catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); } + return ""; + } + public static NameValueCollection DecryptParameters(String encrypted, String base64IV, String base64Key) + { + String fn = ClassName + ".DecryptParameters"; + try + { + String queryString = SacsEncryptionHelper.Decrypt(encrypted, base64IV, base64Key); + //if (SacsSystem.DebugMode) SaLogger.Debug(fn + " \n\tEncrypted: " + encrypted + "\n\tData: " + queryString); + return HttpUtility.ParseQueryString(queryString); + } + catch (Exception exc) { SaLogger.Warn(fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); } + return new NameValueCollection(); + } + + //------------------------------------------------------------------- + // Cookies. + //------------------------------------------------------------------- + /// Retrieve the value from the named request cookie. + public String GetRequestCookie(String key) + { + if (Request.Cookies.ContainsKey(key)) + return Request.Cookies[key]; + return null; + } + /// Set the value against the named response cookie. + public void SetResponseCookie(string key, string value, DateTime? expiresAt = null, bool isEssential = false) + { + CookieOptions options = new CookieOptions(); + options.HttpOnly = true; + options.Secure = true; + options.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict; + if (expiresAt.HasValue) + options.Expires = expiresAt; + if (isEssential) + options.IsEssential = true; + Response.Cookies.Append(key, value, options); + } + + //------------------------------------------------------------------- + // Ajax actions. + //------------------------------------------------------------------- + protected static readonly Encoding ajaxResponseEncoding = Encoding.UTF8; + protected IActionResult GetAjaxResult_GoHome(AjaxResponse_Base responseData) + { + responseData.SetGoHome(); + return Json(responseData); + } + protected IActionResult GetAjaxResult_InvalidSession(AjaxResponse_Base responseData) + { + responseData.SetGoHome(); + return Json(responseData); + } + + //------------------------------------------------------------------- + // Response data. + // Null values are ignored by JSON serialisation. + // - See Saxonite.Net.Core.Web.Base.App.SaxStartup.ConfigureServices() + // - Ref. config.services.AddMvc(...).AddJsonOptions(options => { options.JsonSerializerOptions.IgnoreNullValues = true; }); + //------------------------------------------------------------------- + // Base response data - for simple responses. + public class AjaxResponse_Base + { + // Response status. + public bool? Success { get; set; } = null; + public bool? Error { get; set; } = null; + public bool? SessionExpired { get; protected set; } = null; + public bool? GoHome { get; protected set; } = null; + public AjaxResponse_Base SetSessionExpired(String title, String text) + { + SessionExpired = true; + return SetMessage(title, text); + } + public AjaxResponse_Base SetGoHome() + { + GoHome = true; + return this; + } + public AjaxResponse_Base SetGoHome(String title, String text) + { + GoHome = true; + return SetMessage(title, text); + } + // Message - displays alert popup. + public class MessageData + { + public String Title { get; set; } + public String Text { get; set; } + } + public MessageData Message { get; private set; } + public AjaxResponse_Base SetMessage(String title, String text) + { + if (!StringUtil.IsEmpty(text)) + Message = new MessageData() { Title = title, Text = text }; + return this; + } + } + + //------------------------------------------------------------------- + public class AjaxResponse_Data : AjaxResponse_Base + { + //--------------------------------------------------------------- + // Construction. + protected AjaxResponse_Data(SaModel model) + { + } + + //--------------------------------------------------------------- + /// Item data - Common properties for data items. + public class ItemData_Base + { + // Common item properties. + public String Id { get; set; } + public String Notes { get; set; } + public String StatusCode { get; set; } + public String StatusDesc { get; set; } + // Flags for common status values. + // Return true or null so that property is not serialised when false. + public bool? Awaiting { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Awaiting)) ? true : (bool?)null; } } + public bool? Active { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Active)) ? true : (bool?)null; } } + public bool? Inactive { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Inactive)) ? true : (bool?)null; } } + public bool? Deleted { get { return StatusCode.EqualsCI(SacsSystem.GetStatusCode(SaStatus.Deleted)) ? true : (bool?)null; } } + /// Created on date - YYYYMMDD + public String CreatedOn { get; set; } + /// Created on date - formatted per locale + public String CreatedOnFmt { get; set; } + /// Updated on date - YYYYMMDD + public String UpdatedOn { get; set; } + /// Updated on date - formatted per locale + public String UpdatedOnFmt { get; set; } + } + /// Contact Method properties. + public class ContactMethodData + { + public String Method { get; set; } + public String MethodCaption { get; set; } + public String Value { get; set; } + } + /// Address properties. + public class AddressData : ItemData_Base + { + public String Line1 { get; set; } + public String Line2 { get; set; } + public String Line3 { get; set; } + public String Town { get; set; } + public String RegionIdProvince { get; set; } + public String RegionNameProvince { get; set; } + public String RegionIdCountry { get; set; } + public String RegionNameCountry { get; set; } + public String PostalCode { get; set; } + } + /// + /// Item data with label and value. + /// + public class ItemLabelValue + { + public String Label { get; set; } + public String Value { get; set; } + } + /// + /// Item data with just Id and name. + /// - Id is String to allow for obfuscated values. + /// + public class ItemIdName + { + public String Id { get; set; } + public String Name { get; set; } + } + /// Item code and desc values. + public class ItemCodeDesc + { + public String Code { get; set; } + public String Desc { get; set; } + } + + //--------------------------------------------------------------- + // Form data. + public class FormControlValue + { + public FormControlValue(String controlName, String value, String error) + { + ControlName = controlName; + Value = value; + Error = error; + } + public String ControlName { get; } + public String Value { get; set; } + public String Error { get; set; } + } + private readonly Dictionary controlValues = new Dictionary(); + public List ControlValues { get { return controlValues.Count > 0 ? controlValues.Values.ToList() : null; } } + public void SetControlValue(String controlName, String value, String error = null) + { + if (controlValues.ContainsKey(controlName)) + { + // Modify existing entry in the list. + FormControlValue controlValue = controlValues[controlName]; + if (value != null) controlValue.Value = value; + if (error != null) controlValue.Error = error; + } + else + { + // Add new control to list. + controlValues.Add(controlName, new FormControlValue(controlName, value, error)); + } + } + /// Assign the error to the control by name and return this response data. + public AjaxResponse_Data SetControlError(String controlName, String error) + { + return SetControlError(controlName, error, this); + } + /// + /// Assign the error to the control by name and return the specified value. + /// - Allows alternative return values to be specified (eg. 'false') to avoid separate return statement in calling routine. + /// + public T SetControlError(String controlName, String error, T returnValue) + { + SetControlValue(controlName, null, error); + return returnValue; + } + public bool HasControlErrors() + { + foreach (FormControlValue controlValue in controlValues.Values) + { + if (!StringUtil.IsEmpty(controlValue.Error)) return true; + } + return false; + } + + //--------------------------------------------------------------- + // Notification - briefly displays after response received. + public class NotificationData + { + public String Title { get; set; } + public String Message { get; set; } + } + public NotificationData Notification { get; private set; } + public AjaxResponse_Data SetNotification(String title, String message) + { + Notification = new NotificationData() { Title = title, Message = message }; + return this; + } + + //--------------------------------------------------------------- + // Paged data. + public class PagingData + { + public PagingData(SaModel model) : + this(model.Paging.PageNo, model.Paging.PageLength, model.Paging.ItemCount, model.Paging.ItemTotal) + { } + public PagingData(uint pageNo, ushort pageLength, ushort itemCount, ulong itemTotal) + { + PageNo = pageNo; + PageLength = pageLength; + ItemCount = itemCount; + ItemTotal = itemTotal; + } + public bool Paged { get { return (PageNo > 0 && PageLength > 0 && ItemTotal > PageLength); } } + public uint PageNo { get; } = 0; + public ushort PageLength { get; } = 0; + public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / PageLength) : 0); } } + public ushort ItemCount { get; } = 0; + public ulong ItemTotal { get; } = 0; + } + public PagingData Paging { get; private set; } + public void SetPaging(SaModel model) + { + Paging = new PagingData(model); + } + + //--------------------------------------------------------------- + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsCoreWeb/Web/Components/SaModel.cs b/SacsCoreWeb/Web/Components/SaModel.cs new file mode 100644 index 0000000..0e51ae4 --- /dev/null +++ b/SacsCoreWeb/Web/Components/SaModel.cs @@ -0,0 +1,150 @@ +using Sarand.Net.Standard.Base.App; +using Sarand.Net.Standard.Base.Dbas; +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Sarand.Net.Standard.Base.Bus.SaErrorStateItem; + +namespace Sacs.Web.Components +{ + //======================================================================= + public class SaModel + { + //------------------------------------------------------------------- + // Data members. + private readonly List infoMessages = new List(); + private readonly List warningMessages = new List(); + private readonly List errorMessages = new List(); + + //------------------------------------------------------------------- + /// + /// Protected construction - use derived objects. + /// + protected SaModel() + { + } + + //--------------------------------------------------------------- + // Properties. + public bool DebugMode { get; } = SacsSystem.DebugMode; + public bool OK { get { return !HasMessages(); } } + public virtual bool ProhibitCache { get; set; } = true; + public String PageCaption { get; set; } + public bool IncludeGoogleAnalytics { get; set; } = false; + + //------------------------------------------------------------------- + // Page configuration. + // Views can be registered to avoid multiple inclusions in a page. + private HashSet registeredViews = new HashSet(StringComparer.OrdinalIgnoreCase); + /// + /// Views can be registered to avoid multiple inclusions in a page. + /// Only generate view content if this method returns true. + /// + public bool RegisterView(String viewName) + { +#if DEBUG + if (registeredViews.Contains(viewName)) + SaLogger.Warn(this.GetType().Name + ".RegisterView View already registered: " + viewName); +#endif + return registeredViews.Add(viewName); + } + + //------------------------------------------------------------------- + // Version parameter for includes. + public String HrefVersion + { + get + { + DateTime versionDate = SacsSystem.AssemblyBuildAt; + if (!DateUtil.IsValid(versionDate) || SacsSystem.DebugMode) + versionDate = DateTime.Now; + return DateUtil.Format(versionDate, "yyyyMMdd" /*+ (SacsSystem.Debug ? "HHmm" : "")*/); + } + } + // Additional includes. + public HashSet AdditionalFiles_CSS { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); + public HashSet AdditionalFiles_JS { get; set; } = new HashSet(StringComparer.OrdinalIgnoreCase); + + //------------------------------------------------------------------- + // Redirection locations. + public class RedirectLocationData + { + // Properties populated in Controller_Base.OnActionExecuted. + public String Base { get; set; } + public String Home { get; set; } + public String Home_SessionExpired { get; set; } + } + public RedirectLocationData RedirectLocation { get; } = new RedirectLocationData(); + + //------------------------------------------------------------------- + // Paging properties. + public class DataPaging + { + private DbQuery.Paging paging { get; } = new DbQuery.Paging(); + public void PopulatePagingTo(DbQuery.Paging resultsPaging) + { + resultsPaging.PageLength = paging.PageLength; + resultsPaging.PageNo = paging.PageNo; + } + public bool IsPaged { get { return paging.Paged; } } + public uint PageNo { get { return paging.PageNo; } set { paging.PageNo = value; } } + public String PageNoValue { get { return paging.PageNo.ToString(); } set { paging.PageNo = (uint)StringUtil.ToInt(value); } } + public ushort PageLength { get { return paging.PageLength; } set { paging.PageLength = value; } } + public String PageLengthValue { get { return paging.PageLength.ToString(); } set { paging.PageLength = (ushort)StringUtil.ToShort(value); } } + public uint PageCount { get { return (PageLength > 0 ? (uint)Math.Ceiling((decimal)ItemTotal / paging.PageLength) : 0); } } + public ushort ItemCount { get; set; } + private ulong itemTotal = 0; + public ulong ItemTotal + { + get { return itemTotal; } + set + { + itemTotal = value; + // Check whether the total number of items is less than the current page and reduce page no if needed. + if (IsPaged && (PageNo - 1) * PageLength > itemTotal) + PageNo = (uint)((itemTotal - 1) / PageLength + 1); + } + } + } + public DataPaging Paging { get; } = new DataPaging(); + public void PopulatePagingTo(DbQuery.Paging resultsPaging) + { + Paging.PopulatePagingTo(resultsPaging); + } + + //------------------------------------------------------------------- + // Messages. + //------------------------------------------------------------------- + public bool HasMessages() + { + return infoMessages.Count > 0 || warningMessages.Count > 0 || errorMessages.Count > 0; + } + public List InfoMessages { get { return new List(infoMessages); } } + public List WarningMessages { get { return new List(warningMessages); } } + public List ErrorMessages { get { return new List(errorMessages); } } + + //------------------------------------------------------------------- + // Form messages. + public void AddInfoMessage(String message) + { + if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return; + infoMessages.Add(message); + } + public void AddWarningMessage(String message) + { + if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return; + warningMessages.Add(message); + } + public void AddErrorMessage(String message) + { + if (StringUtil.IsEmpty(StringUtil.TrimAll(message))) return; + errorMessages.Add(message); + } + + //--------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardAccounts/SacsStandardAccounts.csproj b/SacsStandardAccounts/SacsStandardAccounts.csproj index 19fff61..6109294 100644 --- a/SacsStandardAccounts/SacsStandardAccounts.csproj +++ b/SacsStandardAccounts/SacsStandardAccounts.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 Sarand.Net.Standard diff --git a/SacsStandardBase/Base/Core/ColumnDfn.cs b/SacsStandardBase/Base/Core/ColumnDfn.cs index 333d919..f4297a2 100644 --- a/SacsStandardBase/Base/Core/ColumnDfn.cs +++ b/SacsStandardBase/Base/Core/ColumnDfn.cs @@ -98,7 +98,9 @@ namespace Sarand.Net.Standard.Base.Core public String DateTimeSeparator { get; set; } = " "; public String TimeFormat { get; set; } = "HH:mm"; public DataValue.DataType DataType { get; set; } = DataValue.DataType.String; + public bool IsNumeric { get { return DataType == DataValue.DataType.Long || DataType == DataValue.DataType.Double || DataType == DataValue.DataType.Decimal; } } public bool Resizable { get; set; } = true; + public bool WrapText { get; set; } = false; public bool Hidden { get; set; } = false; public bool Frozen { get; set; } = false; // Drop-down configuration. @@ -107,12 +109,11 @@ namespace Sarand.Net.Standard.Base.Core public bool ExclusiveValues { get; set; } = true; /// User entered values (Exclusive=false) are converted if corresponding value found in list. public bool MatchCase { get; set; } = true; - // Edit configuration: + // Edit configuration. public int MaxLength { get; set; } = 0; public bool ReadOnly { get; set; } = false; public bool Uppercase { get; set; } = false; public bool Lowercase { get; set; } = false; - public bool Numeric { get; set; } = false; public bool Password { get; set; } = false; // Sort attributes. /// Sort mode. Default: 'On'. diff --git a/SacsStandardBase/Base/Core/DataValue.cs b/SacsStandardBase/Base/Core/DataValue.cs index d9bf244..e6ef705 100644 --- a/SacsStandardBase/Base/Core/DataValue.cs +++ b/SacsStandardBase/Base/Core/DataValue.cs @@ -172,6 +172,7 @@ namespace Sarand.Net.Standard.Base.Core } return 0.0; } + /// Return value interpretted as boolean. public bool ToBool () { if (value == null) return false; @@ -190,6 +191,7 @@ namespace Sarand.Net.Standard.Base.Core } return false; } + /// Return explicit boolean value or false for other types. public bool AsBool () { // Convert to specific type of data - fails if type is not correct: @@ -369,10 +371,58 @@ namespace Sarand.Net.Standard.Base.Core } //======================================================================= + // Data Value list extension functions. + public static class DataValueList { + //------------------------------------------------------------------- + // Population. + + /// Add a String value to the list. + public static void Add(this List values, String value) + { + values.Add(new DataValue(value)); + } + + /// Add a boolean value to the list. + public static void Add(this List values, bool value) + { + values.Add(new DataValue(value)); + } + + /// Add an integer value to the list. + public static void Add(this List values, int value) + { + values.Add(new DataValue(value)); + } + + /// Add a long value to the list. + public static void Add(this List values, long value) + { + values.Add(new DataValue(value)); + } + + /// Add a double value to the list. + public static void Add(this List values, double value) + { + values.Add(new DataValue(value)); + } + + /// Add a decimal value to the list. + public static void Add(this List values, decimal value) + { + values.Add(new DataValue(value)); + } + + /// Add a date time value to the list. + public static void Add(this List values, DateTime value) + { + values.Add(new DataValue(value)); + } + //------------------------------------------------------------------- // Conversion. + /// Convert all data values to strings. public static List ToStringList (this List values) { @@ -381,6 +431,7 @@ namespace Sarand.Net.Standard.Base.Core stringValues.Add(values.ToString()); return stringValues; } + /// /// Convert the data values to raw objects. /// eg. For population to Excel spreadsheet cells. @@ -409,6 +460,7 @@ namespace Sarand.Net.Standard.Base.Core //------------------------------------------------------------------- // Numeric - only with compatible numeric values. + /// Minimum numeric value in the list. public static DataValue GetMinValue (this List values) { @@ -425,6 +477,7 @@ namespace Sarand.Net.Standard.Base.Core } return minValue; } + /// Maximum numeric value in the list. public static DataValue GetMaxValue (this List values) { diff --git a/SacsStandardBase/Base/Core/SacsDisposable.cs b/SacsStandardBase/Base/Core/SacsDisposable.cs index 3943abd..001e1db 100644 --- a/SacsStandardBase/Base/Core/SacsDisposable.cs +++ b/SacsStandardBase/Base/Core/SacsDisposable.cs @@ -19,7 +19,7 @@ namespace Sarand.Net.Standard.Base.Core // Disposal. ~SacsDisposable () { - Dispose(false); + Dispose(!disposed); } public void Dispose () { diff --git a/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs b/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs index b4491bf..109d0b6 100644 --- a/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs +++ b/SacsStandardBase/Base/Helpers/SacsContentTypeHelper.cs @@ -38,8 +38,20 @@ namespace Sarand.Net.Standard.Base.Helpers public new String PreferredContentType { set { base.PreferredContentType = value; } } } + //------------------------------------------------------------------- + // Definitions. + /// Excel legacy - xls. + public static readonly String ContentType_Excel_Legacy = "application/vnd.ms-excel"; + /// Excel open - xlsx. + public static readonly String ContentType_Excel_Open = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + /// Excel macro enabled - xlsm. + public static readonly String ContentType_Excel_MacroEnabled = "application/vnd.ms-excel.sheet.macroEnabled.12"; + /// Excel binary macro enabled - xlsb. + public static readonly String ContentType_Excel_BinaryMacroEnabled = "application/vnd.ms-excel.sheet.binary.macroEnabled.12"; + //------------------------------------------------------------------- // Data members. + static String ContentType_Generic = "application/octet-stream"; private static readonly List contentTypes; private static FileExtensionContentTypeProvider provider = null; private static readonly HashSet allowedMimeTypes_CV = new HashSet() @@ -87,8 +99,8 @@ namespace Sarand.Net.Standard.Base.Helpers new ContentTypeData_Internal("docx", "Microsoft Word (OpenXML)", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Sacs.MimeType.MsDocx), new ContentTypeData_Internal("odt", "OpenDocument text document", "application/vnd.oasis.opendocument.text", Sacs.MimeType.OpenDocText), // Spreadsheets. - new ContentTypeData_Internal("xls", "Microsoft Excel", "application/vnd.ms-excel", Sacs.MimeType.MsXls), - new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Sacs.MimeType.MsXlsx), + new ContentTypeData_Internal("xls", "Microsoft Excel", ContentType_Excel_Legacy, Sacs.MimeType.MsXls), + new ContentTypeData_Internal("xlsx", "Microsoft Excel (OpenXML)", ContentType_Excel_Open, Sacs.MimeType.MsXlsx), new ContentTypeData_Internal("ods", "OpenDocument spreadsheet document", "application/vnd.oasis.opendocument.spreadsheet", Sacs.MimeType.OpenDocSpreadsheet), // Miscellaneous. new ContentTypeData_Internal("aac", "AAC audio", "audio/aac"), @@ -156,7 +168,7 @@ namespace Sarand.Net.Standard.Base.Helpers //------------------------------------------------------------------- // Properties. /// Generic content type used for 'unknown' document types. - public static String ContentTypeDefault { get; } = "application/octet-stream"; + public static String ContentTypeDefault { get; } = ContentType_Generic; /// Selection of comon content types. public static List ContentTypes { get { return new List(contentTypes); } } public static FileExtensionContentTypeProvider Provider @@ -223,6 +235,20 @@ namespace Sarand.Net.Standard.Base.Helpers } //---------------------------------------------------------------------- + /// Convert the enumerated Mime to content type. + public static String GetContentTypeFromMimeType(Sacs.MimeType mime) + { + if (mime == Sacs.MimeType.Unknown) return ContentTypeDefault; + ContentTypeData contentTypeData = contentTypes.Find(item => item.Mime.Equals(mime)); + if (contentTypeData != null) + return contentTypeData.ContentType; + return ContentTypeDefault; + } + + //---------------------------------------------------------------------- + // Mime type. + //---------------------------------------------------------------------- + /// Convert the content type to enumerated Mime. public static Sacs.MimeType GetMimeTypeFromContentType(this String contentType) { diff --git a/SacsStandardBase/Base/Procs/SaProcess_Base.cs b/SacsStandardBase/Base/Procs/SaProcess_Base.cs index d419018..0d2f073 100644 --- a/SacsStandardBase/Base/Procs/SaProcess_Base.cs +++ b/SacsStandardBase/Base/Procs/SaProcess_Base.cs @@ -1,29 +1,37 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Core; +using Sarand.Net.Standard.Base.Tasks; using Sarand.Net.Standard.Base.Utils; +using static Sarand.Net.Standard.Base.Procs.SaProcess_Base; namespace Sarand.Net.Standard.Base.Procs { - //======================================================================= - public interface ISacsProcessNotifier - { - void OnStartProcess (); - void SetProgressInfo (List progressInfo); - void OnEndProcess (); - } - //======================================================================= public abstract class SaProcess_Base : SacsDisposable { + //------------------------------------------------------------------- + // Exceptions. + [Serializable] + public class InProgressException : Exception + { + public InProgressException() { } + public InProgressException(string message) : base(message) { } + public InProgressException(string message, Exception inner) : base(message, inner) { } + } + //------------------------------------------------------------------- // Process options and groups - for configuration of process to run. // See Sarand.Win.Forms.Utility.SaForm_SelectOptions. public class OptionData { - public OptionData (TypeCodeDesc option) + public OptionData(TypeCodeDesc option) { Option = option; } @@ -33,7 +41,7 @@ namespace Sarand.Net.Standard.Base.Procs } public class GroupData { - public GroupData (TypeCodeDesc optionGroup) + public GroupData(TypeCodeDesc optionGroup) { OptionGroup = optionGroup; } @@ -55,13 +63,13 @@ namespace Sarand.Net.Standard.Base.Procs } public class Config { - protected Config () + protected Config() { } /// Identifies the type of process. public ProcessType ProcessType { get; set; } = ProcessType.Unknown; /// Is the process to be run in 'Test' mode. - public bool TestProcess{ get; set; } = SacsSystem.DebugMode; + public bool TestProcess { get; set; } = SacsSystem.DebugMode; /// Process imports data. public bool Import { get { return ProcessType == ProcessType.ImportData || ProcessType == ProcessType.ImportExportData; } } /// Process exports data. @@ -74,28 +82,131 @@ namespace Sarand.Net.Standard.Base.Procs public bool Automated { get; set; } = false; } + //------------------------------------------------------------------- + // Process control definitions. + + public class ProcessMessage + { + public ProcessMessage(String message, SaMessageLevel level = SaMessageLevel.Info) + { + this.Message = message; + this.Level = level; + } + public String Message { get; } + public SaMessageLevel Level { get; } + public int Instances { get; internal set; } + } + + private class ProcessControlData + { + // Interrupt. + private class InterruptData + { + public readonly Object lockable = new object(); + private bool interrupted = false; + public bool Interrupted { get { lock (lockable) { return interrupted; } } set { lock (lockable) { interrupted = value; } } } + } + private InterruptData interrupt { get; } = new InterruptData(); + public bool Interrupted { get { return interrupt.Interrupted; } set { interrupt.Interrupted = value; } } + + // Process messages. + private class ProcessMessagesData + { + public readonly Object lockable = new object(); + public List messages { get; } = new List(); + } + private ProcessMessagesData processMessages { get; } = new ProcessMessagesData(); + /// Convert the message levele to one of the 3 values used with Process Messages: Info, Warning, Error. + public static SaMessageLevel GetLevel(SaMessageLevel level) + { + if (level == SaMessageLevel.Error || level == SaMessageLevel.Fatal || level == SaMessageLevel.Exception) + level = SaMessageLevel.Error; + else if (level == SaMessageLevel.Warning) + level = SaMessageLevel.Warning; + else + level = SaMessageLevel.Info; + return level; + } + /// Add a message of the specified level (Info, Warning, Error). + public bool AddProcessMessage(String message, SaMessageLevel level) + { + message = StringUtil.TrimWhitespace(message); + if (StringUtil.IsBlank(message)) return false; + level = GetLevel(level); + lock (processMessages.lockable) + { + ProcessMessage procMsg = processMessages.messages.Find(item => item.Message.EqualsCI(message) && item.Level == level); + // Duplicate messages increment the 'Instances' value. + if (procMsg != null) + procMsg.Instances++; + // Otherwise add a new message to the list. + else processMessages.messages.Add(new ProcessMessage(message, level)); + } + return true; + } + /// Are there any message of the specified level (Info, Warning, Error). + public bool HasProcessMessages(SaMessageLevel level) + { + lock (processMessages.lockable) + return processMessages.messages.Exists(item => item.Level == GetLevel(level)); + } + /// Get the first message of the specified level (Info, Warning, Error). + public String GetProcessMessage(SaMessageLevel level) + { + lock (processMessages.lockable) + return processMessages.messages.Find(item => item.Level == GetLevel(level))?.Message ?? ""; + } + /// Return all process messages. + public List GetProcessMessages() + { + List messages = new List(); + lock (processMessages.lockable) + messages.AddRange(processMessages.messages); + return messages; + } + /// Return all process messages of the specified Level (Info, Warning, Error). + public List GetProcessMessages(SaMessageLevel level) + { + List messages = new List(); + lock (processMessages.lockable) + messages.AddRange(processMessages.messages.FindAll(item => item.Level == GetLevel(level))); + return messages; + } + /// Clear process messages. + public void ClearProcessMessages() + { + lock (processMessages.lockable) + processMessages.messages.Clear(); + } + + // Task control data. + private SacsTaskData taskData = null; + public SacsTaskData TaskData { get { return taskData; } set { if (taskData != null) taskData.Dispose(); taskData = value; } } + } + //------------------------------------------------------------------- // Data members. private Config config; - private String processError = ""; + private ProcessControlData Control { get; } = new ProcessControlData(); //------------------------------------------------------------------- // Construction. - protected SaProcess_Base (Config config) + protected SaProcess_Base(Config config) { this.config = config; } - protected override void Dispose (bool disposing) + protected override void Dispose(bool disposing) { if (disposing) { + // Clear process data to dispose. + Control.TaskData = null; } } //------------------------------------------------------------------- // Properties. public bool DebugMode { get { return SacsSystem.DebugMode; } } - public ISacsProcessNotifier ProcessNotifier { get; set; } = null; public bool Processing { get; protected set; } = false; public bool ProcessEnded { get; protected set; } = false; public DateTime StartedAt { get; private set; } = DateTime.MinValue; @@ -120,56 +231,195 @@ namespace Sarand.Net.Standard.Base.Procs public virtual List ProcessingStatistics { get { return new List(); } } //------------------------------------------------------------------- - // Run process inline. - public bool RunProcess () + // Run process asynchronously. + private class AsyncProcessData : SacsDisposable { - String fn = GetType().Name+".RunProcess"; - if (ProcessNotifier == null) - throw new NotSupportedException("Process Notifier must be specified"); + public AsyncProcessData(Task task) + { + this.Task = task; + } + protected override void Dispose(bool disposing) + { + if (disposing) { } + } + public Task Task { get; } + } + private AsyncProcessData AsyncData { get; set; } + public bool IsAsync { get { return AsyncData != null; } } + private void ClearAsyncData() + { + if (AsyncData != null) + { + lock (AsyncData) + { + AsyncData.Dispose(); + AsyncData = null; + } + } + } + /// + /// Run the process asynchronously. + /// + /// + /// If process is already running. + public async void RunAsync() + { + if (AsyncData != null) + throw new InProgressException(); + Control.TaskData = new SacsTaskData(); + // Run on separate thread. + Task task = new Task(() => + { + try + { + // Execute the process. + RunProcess(); + } + catch (OperationCanceledException exc) + { + SaLogger.Warn(exc.GetType().Name + " during asynchronous processing on " + GetType().Name); + } + finally + { + // Clear processing data. + ClearAsyncData(); + } + }, Control.TaskData.CancellationToken); + AsyncData = new AsyncProcessData(task); + task.Start(); + await task; + } + + //------------------------------------------------------------------- + // Run process inline. + public bool RunProcess() + { + String fn = GetType().Name + ".RunProcess"; // Prepare process. bool runProcess = false; Reset(); + if (Control.TaskData == null) + Control.TaskData = new SacsTaskData(); Processing = true; StartedAt = DateTime.Now; + + // Process starting. try { // Notify start of process and log/ignore exceptions. - ProcessNotifier.OnStartProcess(); + InvokeProcessStarting(); } catch (Exception exc) { - SaLogger.Error(fn + " OnStartProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + String msg = exc.GetType().Name + ": " + exc.Message; + AddErrorMessage(msg); + SaLogger.Error(fn + " ProcessStarting: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true); } + + // Process running. try { // Run process. - OnProcessStarting(); runProcess = Run(); - OnProcessEnding(); } catch (Exception exc) { - SetProcessError(exc.GetType().Name + ": " + exc.Message); - SaLogger.Error(fn + " " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + String msg = exc.GetType().Name + ": " + exc.Message; + AddErrorMessage(msg); + SaLogger.Error(fn + " " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true); } - // Processing completed. - FinishedAt = DateTime.Now; - Processing = false; - ProcessEnded = true; + finally + { + // Processing completed. + FinishedAt = DateTime.Now; + Processing = false; + ProcessEnded = true; + Control.TaskData = null; + } + + // Process completed. try { - // Notify end of process and log/ignore exceptions. - ProcessNotifier.OnEndProcess(); + // Notify completion of process and log/ignore exceptions. + InvokeProcessCompleted(); } catch (Exception exc) { - SaLogger.Error(fn + " OnEndProcess: " + GetProcessError() + exc.GetStackEntries(6).Concat("", "\n\tat "), true); + String msg = exc.GetType().Name + ": " + exc.Message; + AddErrorMessage(msg); + SaLogger.Error(fn + " ProcessCompleted: " + msg + exc.GetStackEntries(6).Concat("", "\n\tat "), true); } return runProcess; } - protected abstract bool Run (); - protected virtual void OnProcessStarting () { } - protected virtual void OnProcessEnding () { } + protected abstract bool Run(); + + //------------------------------------------------------------------- + public async Task StopProcessAsync(int timeout = 50) + { + if (!Processing) return; + if (AsyncData != null) + { + try + { + // Cancel the task. + Control.TaskData.TriggerCancellation(); + // Wait for task termination or timeout. + if (await Task.WhenAny(AsyncData.Task, Task.Delay(Math.Max(20, Math.Min(1000, timeout)))) != AsyncData.Task) + { + // Timeout completed before task - force termination. + Control.TaskData.ThrowIfCancellationRequested(); + } + } + finally + { + Control.TaskData = null; + ClearAsyncData(); + } + } + } + + //------------------------------------------------------------------- + // Processing events. + + // Processing Starting. + public class ProcessStartingEventArgs : EventArgs { } + public event EventHandler OnProcessStarting; + protected virtual void InvokeProcessStarting() + { + if (OnProcessStarting != null) + { + ProcessStartingEventArgs args = new ProcessStartingEventArgs(); + OnProcessStarting(this, args); + } + } + + // Process Info Updated. + public class ProgressInfoUpdatedEventArgs : EventArgs + { + public List ProgressInfo { get; } = new List(); + } + public event EventHandler OnProgressInfoUpdated; + protected virtual void InvokeProgressInfoUpdated(List progressInfo) + { + if (OnProgressInfoUpdated != null) + { + ProgressInfoUpdatedEventArgs args = new ProgressInfoUpdatedEventArgs(); + args.ProgressInfo.AddRange(progressInfo); + OnProgressInfoUpdated(this, args); + } + } + + // Processing Completed. + public class ProcessCompletedEventArgs : EventArgs { } + public event EventHandler OnProcessCompleted; + protected virtual void InvokeProcessCompleted() + { + if (OnProcessCompleted != null) + { + ProcessCompletedEventArgs args = new ProcessCompletedEventArgs(); + OnProcessCompleted(this, args); + } + } //------------------------------------------------------------------- // Process control. @@ -177,15 +427,15 @@ namespace Sarand.Net.Standard.Base.Procs { get { - if (HasProcessError()) return false; - if (isInterrupted) return false; + if (HasErrorMessage()) return false; + if (Control.Interrupted) return false; return true; } } - private void Reset () + private void Reset() { - ClearProcessError(); - isInterrupted = false; + Control.ClearProcessMessages(); + Control.Interrupted = false; ProcessEnded = false; StartedAt = DateTime.MinValue; FinishedAt = DateTime.MinValue; @@ -193,36 +443,70 @@ namespace Sarand.Net.Standard.Base.Procs //------------------------------------------------------------------- // Progress information. - protected void SetProgressInfo (String progressInfo) + protected void SetProgressInfo(String progressInfo) { SetProgressInfo(StringListUtil.CreateList(progressInfo)); } - protected void SetProgressInfo (List progressInfo) + protected void SetProgressInfo(List progressInfo) { - if (ProcessNotifier != null) - ProcessNotifier.SetProgressInfo(progressInfo); + InvokeProgressInfoUpdated(progressInfo); } //------------------------------------------------------------------- - // Error handling. - public bool HasProcessError () { return !StringUtil.IsEmpty(processError); } - public String GetProcessError () { return StringUtil.EmptyOnNull(processError); } - public void ClearProcessError () { processError = ""; } - /// Record the processing error - always returns false. - public bool SetProcessError (String error) + // Process messages. + /// Add a message of the specified level (Info, Warning, Error). + protected void AddMessage(String message, SaMessageLevel level) { - processError = StringUtil.EmptyOnNull(error); + if (Control.AddProcessMessage(message, level)) + SaLogger.Log(GetType().Name + " " + message, ProcessControlData.GetLevel(level)); + } + protected void AddInfoMessage(String message) + { + AddMessage(message, SaMessageLevel.Info); + } + protected void AddWarningMessage(String message) + { + AddMessage(message, SaMessageLevel.Warning); + } + /// Add the error message and always return false. + protected bool AddErrorMessage(String message) + { + AddMessage(message, SaMessageLevel.Error); return false; } + /// Are there any error messages. + public bool HasErrorMessage() + { + return Control.HasProcessMessages(SaMessageLevel.Error); + } + /// Get the first error message. + public String GetErrorMessage() + { + return Control.GetProcessMessage(SaMessageLevel.Error); + } + /// Return all process messages. + public List GetProcessMessages() + { + return Control.GetProcessMessages(); + } + /// Return all process messages of the specified Level (Info, Warning, Error). + public List GetProcessMessages(SaMessageLevel level) + { + return Control.GetProcessMessages(level); + } + /// Clear process messages. + public void ClearProcessMessages() + { + Control.ClearProcessMessages(); + } //------------------------------------------------------------------- // Interruption. - private bool isInterrupted = false; public bool IsInterrupted { - get { return isInterrupted; } + get { return Control.Interrupted; } } - public bool Interrupt () + public bool Interrupt() { if (!config.AllowInterruption) { @@ -230,10 +514,10 @@ namespace Sarand.Net.Standard.Base.Procs return false; } if (config.ConfirmInterruption && !IsInterruptionConfirmed()) return false; - isInterrupted = true; + Control.Interrupted = true; return true; } - protected virtual bool IsInterruptionConfirmed () + protected virtual bool IsInterruptionConfirmed() { return true; } diff --git a/SacsStandardBase/Base/Tasks/SacsTaskData.cs b/SacsStandardBase/Base/Tasks/SacsTaskData.cs index c82afe3..a7bdfe5 100644 --- a/SacsStandardBase/Base/Tasks/SacsTaskData.cs +++ b/SacsStandardBase/Base/Tasks/SacsTaskData.cs @@ -17,7 +17,7 @@ namespace Sarand.Net.Standard.Base.Tasks //------------------------------------------------------------------- // Construction. - public SacsTaskData (CancellationToken? cancellationToken) + public SacsTaskData (CancellationToken? cancellationToken = null) { if (cancellationToken != null) { @@ -49,18 +49,22 @@ namespace Sarand.Net.Standard.Base.Tasks // Properties. public bool Debug { get; set; } + /// Token to allow the async request to be cancelled. + public CancellationToken CancellationToken { get; } + //------------------------------------------------------------------- // C a n c e l l a t i o n //------------------------------------------------------------------- - /// Token to allow the async request to be cancelled. - public CancellationToken CancellationToken { get; } + /// Gets whether cancellation has been requested. public bool IsCancellationRequested { get { return CancellationToken.IsCancellationRequested; } } + /// Throws a System.OperationCanceledException if this token has had cancellation requested. public void ThrowIfCancellationRequested() { CancellationToken.ThrowIfCancellationRequested(); } + /// Can cancellation be triggered here? public bool CanBeCancelled { get { return cancellationTokenSource != null; } } public bool TriggerCancellation () diff --git a/SacsStandardBase/Base/Utils/ImageUtils.cs b/SacsStandardBase/Base/Utils/ImageUtils.cs index 5c9d559..8c4772d 100644 --- a/SacsStandardBase/Base/Utils/ImageUtils.cs +++ b/SacsStandardBase/Base/Utils/ImageUtils.cs @@ -79,7 +79,8 @@ namespace Sarand.Net.Standard.Base.Utils /// public static SixLabors.ImageSharp.Image LoadImage(byte[] imageData, out IImageFormat format) { - var image = Image.Load(imageData, out format); + var image = Image.Load(imageData); + format = Image.DetectFormat(imageData); return image; } @@ -90,7 +91,7 @@ namespace Sarand.Net.Standard.Base.Utils { using (MemoryStream ms = new MemoryStream()) { - var imageEncoder = image.GetConfiguration().ImageFormatsManager.FindEncoder(imageFormat); + var imageEncoder = image.Configuration.ImageFormatsManager.GetEncoder(imageFormat); image.Save(ms, imageEncoder); return ms.ToArray(); } diff --git a/SacsStandardBase/Base/Utils/SerializableUtil.cs b/SacsStandardBase/Base/Utils/SerializableUtil.cs index 4f6a305..5e35e73 100644 --- a/SacsStandardBase/Base/Utils/SerializableUtil.cs +++ b/SacsStandardBase/Base/Utils/SerializableUtil.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; @@ -26,32 +27,24 @@ namespace Sarand.Net.Standard.Base.Utils if (Object.ReferenceEquals(source, null)) return default(T); - IFormatter formatter = new BinaryFormatter(); - Stream stream = new MemoryStream(); - using (stream) - { - formatter.Serialize(stream, source); - stream.Seek(0, SeekOrigin.Begin); - return (T)formatter.Deserialize(stream); - } + // initialize inner objects individually + // for example in default constructor some list property initialized with some values, + // but in 'source' these items are cleaned - + // without ObjectCreationHandling.Replace default constructor values will be added to result + var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace }; + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(source), deserializeSettings); } //------------------------------------------------------------------- - // Serialize. - public static String Serialize(T source) + // Serialize using JSON. + public static String ToJson(T source) { if (!typeof(T).IsSerializable) throw new ArgumentException("The type must be serializable.", "source"); // Don't serialize a null object, simply return the default for that object if (Object.ReferenceEquals(source, null)) return ""; - // Serialise to String. - IFormatter formatter = new BinaryFormatter(); - using (Stream stream = new MemoryStream()) - using (StreamReader streamReader = new StreamReader(stream)) - { - formatter.Serialize(stream, source); - stream.Seek(0, SeekOrigin.Begin); - return streamReader.ReadToEnd(); - } + // Serialise to JSON String. + return JsonConvert.SerializeObject(source); } //------------------------------------------------------------------- diff --git a/SacsStandardBase/Base/Utils/StringUtil.cs b/SacsStandardBase/Base/Utils/StringUtil.cs index 26fbaad..a780247 100644 --- a/SacsStandardBase/Base/Utils/StringUtil.cs +++ b/SacsStandardBase/Base/Utils/StringUtil.cs @@ -231,7 +231,20 @@ namespace Sarand.Net.Standard.Base.Utils Regex regexMultipleInternal = new Regex("[" + Regex.Escape(new String(trimChars)) + "]{2,}", RegexOptions.None); return regexMultipleInternal.Replace(value, replacement); } + + private static Regex regexWhitespace = new Regex("\\s+", RegexOptions.Compiled); + /// + /// Trim all leading and trailling whitespace and replace internal whitespace with a single space. + /// + public static String TrimWhitespace(this String value) + { + return regexWhitespace.Replace(value, " ").Trim(); + } + private static Regex regexMultipleSpaces = new Regex("[ ]{2,}", RegexOptions.Compiled); + /// + /// Replace multiple spaces with a single space. + /// public static String TrimInternalSpaces(this String value) { return regexMultipleSpaces.Replace(value, " "); @@ -540,6 +553,7 @@ namespace Sarand.Net.Standard.Base.Utils if (value.ToDouble() != 0.0) return true; if (value.Left(1).EqualsCI("Y")) return true; if (value.EqualsCI("True")) return true; + if (value.EqualsCI("On")) return true; return false; } diff --git a/SacsStandardBase/Base/Utils/SysUtil.cs b/SacsStandardBase/Base/Utils/SysUtil.cs index 7d00cd5..1af6b75 100644 --- a/SacsStandardBase/Base/Utils/SysUtil.cs +++ b/SacsStandardBase/Base/Utils/SysUtil.cs @@ -1,13 +1,82 @@ -namespace Sarand.Net.Standard.Base.Utils +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Sarand.Net.Standard.Base.Utils { //======================================================================= - public class SysUtil + public static class SysUtil { //------------------------------------------------------------------- - // Data members. + /// Get calling code's class name. + /// + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetCallerClassName() + { + try + { + var stackFrame = new StackTrace().GetFrame(1); + var methodInfo = stackFrame.GetMethod(); + return methodInfo.ReflectedType.Name.TrimWhitespace(); + } + catch (Exception) { } + return ""; + } + + /// Get calling code's fucntion name. + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetCallerMemberName([CallerMemberName] string memberName = "") + { + return memberName; + } + + /// Get calling code's file path. + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetCallerFilePath([CallerFilePath] string filePath = "") + { + return filePath; + } + + /// Get calling code's line number. + [MethodImpl(MethodImplOptions.NoInlining)] + public static int GetCallerLineNumber([CallerLineNumber] int lineNumber = 0) + { + return lineNumber; + } + //------------------------------------------------------------------- - // Construction. - private SysUtil () { } + /// Shortcut to GetCallerClassMethod() - returns [class.method] for calling code. + public static string Fn { get { return GetCallerClassMethod(2); } } + + /// Return identity of calling method as [class.method]. + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetCallerClassMethod(int stackFrameIdx = 1) + { + try + { + var stackFrame = new StackTrace().GetFrame(stackFrameIdx); + var methodInfo = stackFrame.GetMethod(); + return (methodInfo.ReflectedType.Name + "." + methodInfo.Name).TrimWhitespace().Trim('.'); + } + catch (Exception) { } + return ""; + } + + //------------------------------------------------------------------- + /// Return identity of calling method as [class.method:line]. + [MethodImpl(MethodImplOptions.NoInlining)] + public static string GetCallerClassMethodLine() + { + try + { + var stackFrame = new StackTrace().GetFrame(1); + var methodInfo = stackFrame.GetMethod(); + return (methodInfo.ReflectedType.Name + "." + methodInfo.Name + ":" + stackFrame.GetFileLineNumber()).TrimWhitespace().Trim('.'); + } + catch (Exception) { } + return ""; + } + //------------------------------------------------------------------- } //======================================================================= diff --git a/SacsStandardBase/SacsStandardBase.csproj b/SacsStandardBase/SacsStandardBase.csproj index c465440..4254e27 100644 --- a/SacsStandardBase/SacsStandardBase.csproj +++ b/SacsStandardBase/SacsStandardBase.csproj @@ -1,17 +1,17 @@ - netstandard2.1 + net6.0 Sarand.Net.Standard - + - - + + diff --git a/SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDriver.cs b/SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDriver.cs index a63f2e7..3fa3904 100644 --- a/SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDriver.cs +++ b/SacsStandardDbas/Dbas/Base/SaDbDfnOdbcDriver.cs @@ -17,7 +17,7 @@ namespace Sarand.Net.Standard.Dbas.Base //------------------------------------------------------------------- // Construction. - public SaDbDfnOdbcDriver (String name) + public SaDbDfnOdbcDriver(String name) { Name = name; } @@ -25,28 +25,30 @@ namespace Sarand.Net.Standard.Dbas.Base //------------------------------------------------------------------- // Properties. public String Name { get; } - public Dictionary Attributes { get; } = new Dictionary(); + public Dictionary Attributes { get; } = new Dictionary(); //------------------------------------------------------------------- // Utilities. /// Add 'Driver' attribute from registry. Fails if driver name not found. - public bool AddDriverConfig () + public bool AddDriverConfig() { if (StringUtil.IsEmpty(Name)) return false; try { +#pragma warning disable CA1416 // Retrieve 'Driver' (location of DLL) parameter for this driver: using (RegistryKey localMachineHive = Registry.LocalMachine) using (RegistryKey odbcDriverKey = localMachineHive.OpenSubKey(@"SOFTWARE\ODBC\ODBCINST.INI\" + Name)) { if (odbcDriverKey != null) { - object driverName = odbcDriverKey.GetValue("Driver"); + object driverName = odbcDriverKey.GetValue("Driver"); if (driverName != null && driverName is String) Attributes["Driver"] = driverName.ToString(); } return true; } +#pragma warning restore CA1416 } catch (Exception) { } return true; diff --git a/SacsStandardDbas/Dbas/Base/SacsDatabase.cs b/SacsStandardDbas/Dbas/Base/SacsDatabase.cs index 2dca537..9f1b1ea 100644 --- a/SacsStandardDbas/Dbas/Base/SacsDatabase.cs +++ b/SacsStandardDbas/Dbas/Base/SacsDatabase.cs @@ -22,7 +22,7 @@ namespace Sarand.Net.Standard.Dbas.Base //------------------------------------------------------------------- // Construction. - protected SacsDatabase (DbmsType dbms, String databaseName, String connectionString) + protected SacsDatabase(DbmsType dbms, String databaseName, String connectionString) { DbmsType = dbms; DatabaseName = databaseName; @@ -36,9 +36,9 @@ namespace Sarand.Net.Standard.Dbas.Base public String DatabaseName { get; } public SelectLockMode SelectLockMode { get; set; } = SelectLockMode.Default; public bool DisplayErrors { get; set; } = true; - public abstract void OnConnectionChanged (); - public abstract DbConnection CreateConnection (bool forceOdbc = false); - public abstract DbDataAdapter CreateDataAdapter (); + public abstract void OnConnectionChanged(); + public abstract DbConnection CreateConnection(bool forceOdbc = false); + public abstract DbDataAdapter CreateDataAdapter(); // Profile properties. public static String ProfileSection_DbUtilities { get; } = "DbUtilities"; public static String ProfileValue_DbExportFile { get; } = "DbExportFile"; @@ -57,21 +57,21 @@ namespace Sarand.Net.Standard.Dbas.Base protected String NextIdTable { get; set; } = "NextId"; protected String NextIdColumn_Object { get; set; } = "Object"; protected String NextIdColumn_NextId { get; set; } = "NextId"; - public abstract SysId GetNextId (String objectName, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id"); + public abstract SysId GetNextId(String objectName, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id"); public abstract int GetNextIdFromTable(string objectName, string idColumn = "Id"); - public abstract bool SetMinNextId (String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false); + public abstract bool SetMinNextId(String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false); //------------------------------------------------------------------- // O D B C D r i v e r s //------------------------------------------------------------------- [DllImport("odbccp32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern bool SQLGetInstalledDriversW (char[] lpszBuf, ushort cbufMax, out ushort pcbBufOut); + private static extern bool SQLGetInstalledDriversW(char[] lpszBuf, ushort cbufMax, out ushort pcbBufOut); /// /// Gets the ODBC driver names from the SQLGetInstalledDrivers function. /// Ref. https://stackoverflow.com/questions/6457973/odbc-driver-list-from-net /// /// List of ODBC driver names, if successfull. - public static List GetOdbcDriverNames () + public static List GetOdbcDriverNames() { try { @@ -93,13 +93,16 @@ namespace Sarand.Net.Standard.Dbas.Base catch (Exception) { } try { +#pragma warning disable CA1416 // Alternatively, use the registry. + // Validate platform compatibility using (RegistryKey localMachineHive = Registry.LocalMachine) using (RegistryKey odbcDriversKey = localMachineHive.OpenSubKey(@"SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers")) { if (odbcDriversKey != null) return new List(odbcDriversKey.GetValueNames()).OrderBy(item => item).ToList(); } + // Validate platform compatibility // 64-BitConverter machines. using (RegistryKey localMachineHive = Registry.LocalMachine) using (RegistryKey odbcDriversKey = localMachineHive.OpenSubKey(@"SOFTWARE\Wow6432Node\ODBC\ODBCINST.INI\ODBC Drivers")) @@ -107,6 +110,7 @@ namespace Sarand.Net.Standard.Dbas.Base if (odbcDriversKey != null) return new List(odbcDriversKey.GetValueNames()).OrderBy(item => item).ToList(); } +#pragma warning restore CA1416 } catch (Exception) { } return new List(); @@ -116,13 +120,13 @@ namespace Sarand.Net.Standard.Dbas.Base // O D B C D a t a S o u r c e s //------------------------------------------------------------------- [DllImport("odbc32.dll")] - public static extern int SQLDataSources (int EnvHandle, int Direction, StringBuilder ServerName, int ServerNameBufferLenIn, ref int ServerNameBufferLenOut, StringBuilder Driver, int DriverBufferLenIn, ref int DriverBufferLenOut); + public static extern int SQLDataSources(int EnvHandle, int Direction, StringBuilder ServerName, int ServerNameBufferLenIn, ref int ServerNameBufferLenOut, StringBuilder Driver, int DriverBufferLenIn, ref int DriverBufferLenOut); [DllImport("odbc32.dll")] - public static extern int SQLAllocEnv (ref int EnvHandle); - public static List GetOdbcDatasources () + public static extern int SQLAllocEnv(ref int EnvHandle); + public static List GetOdbcDatasources() { List dataSources = new List(); - int envHandle=0; + int envHandle = 0; const int SQL_FETCH_NEXT = 1; const int SQL_FETCH_FIRST_SYSTEM = 32; @@ -147,7 +151,7 @@ namespace Sarand.Net.Standard.Dbas.Base //------------------------------------------------------------------- // Q u e r y D a t a b a s e //------------------------------------------------------------------- - public String GetSelectQueryPrefix () + public String GetSelectQueryPrefix() { // Query prefix. switch (SelectLockMode) @@ -180,16 +184,16 @@ namespace Sarand.Net.Standard.Dbas.Base } return ""; } - public abstract IDbDataTable ExecuteQuery (String query); - public abstract int ExecuteNonQuery (String query); - public abstract int ExecuteInsert (String tableName, List columnNameValues); - public abstract int ExecuteUpdate (String tableName, List columnNameValues, String filter); - public abstract int ExecuteDelete (String tableName, String filter); + public abstract IDbDataTable ExecuteQuery(String query); + public abstract int ExecuteNonQuery(String query); + public abstract int ExecuteInsert(String tableName, List columnNameValues); + public abstract int ExecuteUpdate(String tableName, List columnNameValues, String filter); + public abstract int ExecuteDelete(String tableName, String filter); /// /// Add value pair - used to build value lists for insert and update. /// Caters for delimiting text values and uses 'NULL' for empty text values. /// - public static void AddValuePair (List columnNameValues, String columnName, String value, bool delimitString = false) + public static void AddValuePair(List columnNameValues, String columnName, String value, bool delimitString = false) { if (delimitString) { @@ -200,42 +204,42 @@ namespace Sarand.Net.Standard.Dbas.Base columnNameValues.Add(columnName, value); } /// Add SysId value pair - used to build value lists for insert and update. - public static void AddValuePair (List columnNameValues, String columnName, SysId value) + public static void AddValuePair(List columnNameValues, String columnName, SysId value) { columnNameValues.Add(columnName, SysId.ToInt(value).ToString()); } /// Add numeric value pair - used to build value lists for insert and update. - public static void AddValuePair (List columnNameValues, String columnName, long value) + public static void AddValuePair(List columnNameValues, String columnName, long value) { columnNameValues.Add(columnName, value.ToString()); } /// Add decimal value pair - used to build value lists for insert and update. - public static void AddValuePair (List columnNameValues, String columnName, decimal value) + public static void AddValuePair(List columnNameValues, String columnName, decimal value) { columnNameValues.Add(columnName, value.ToString()); } /// Add date/time value pair - used to build value lists for insert and update. - public static void AddValuePair (List columnNameValues, String columnName, DateTime value) + public static void AddValuePair(List columnNameValues, String columnName, DateTime value) { AddValuePair(columnNameValues, columnName, DateUtil.Format(value, "yyyy/MM/dd HH:mm:ss"), true); } // Specific queries. - public abstract long GetRowCount (String tableName, String filter); - public abstract DataValue GetRowValue (String tableName, String columnName, String filter); - public abstract DataValue GetRowValue (String tableName, String columnName, String filter, DbGetValueType getValueType); + public abstract long GetRowCount(String tableName, String filter); + public abstract DataValue GetRowValue(String tableName, String columnName, String filter); + public abstract DataValue GetRowValue(String tableName, String columnName, String filter, DbGetValueType getValueType); //------------------------------------------------------------------- // D a t a b a s e S c h e m a //------------------------------------------------------------------- - public abstract List GetTableNames (bool refresh = false); - public abstract List GetTableDfns (bool refresh = false); - public abstract DbDfnTable GetTableDfn (String name, bool refresh = false); - public bool TableExists (String tableName) + public abstract List GetTableNames(bool refresh = false); + public abstract List GetTableDfns(bool refresh = false); + public abstract DbDfnTable GetTableDfn(String name, bool refresh = false); + public bool TableExists(String tableName) { if (StringUtil.IsEmpty(tableName)) return false; return GetTableNames().Exists(table => table.EqualsCI(tableName)); } - public bool ColumnExists (String tableName, String columnName) + public bool ColumnExists(String tableName, String columnName) { if (StringUtil.IsEmpty(tableName) || StringUtil.IsEmpty(columnName)) return false; DbDfnTable table = GetTableDfn(tableName); @@ -243,7 +247,7 @@ namespace Sarand.Net.Standard.Dbas.Base return table.HasColumnByName(columnName); return false; } - public bool IndexExists (String tableName, String indexName) + public bool IndexExists(String tableName, String indexName) { if (StringUtil.IsEmpty(tableName) || StringUtil.IsEmpty(indexName)) return false; DbDfnTable table = GetTableDfn(tableName); @@ -263,7 +267,7 @@ namespace Sarand.Net.Standard.Dbas.Base // Logging. public static SaMessageLevel LoggingLevel { get; set; } = SaMessageLevel.None; public static String LoggingPrefix { get; set; } = " ~~~ "; - protected static void Log (String fn, String message, SaMessageLevel level = SaMessageLevel.Debug) + protected static void Log(String fn, String message, SaMessageLevel level = SaMessageLevel.Debug) { if ((int)level > (int)LoggingLevel) return; SaLogger.Log((fn + "\n\t" + message).Trim(), level); diff --git a/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeType_Db.cs b/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeType_Db.cs index 28e2dbb..6e4fc6a 100644 --- a/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeType_Db.cs +++ b/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCodeType_Db.cs @@ -5,7 +5,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; diff --git a/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCode_Db.cs b/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCode_Db.cs index f0b14a1..5e39256 100644 --- a/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCode_Db.cs +++ b/SacsStandardDbas/Dbas/CodeTypes/Internal/SaCode_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs b/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs index 74475a1..f62fcc7 100644 --- a/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs +++ b/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs @@ -173,10 +173,9 @@ namespace Sarand.Net.Standard.Dbas.Data // Query records. public override IDbDataTable ExecuteQuery(String query) { - String fn = GetType().Name + ".ExecuteQuery"; ClearErrorState(); DataTable dataTable = null; - Log(fn, "Query: " + query, SaMessageLevel.Debug); + Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); try { using (DbConnection dbConnection = CreateConnection()) @@ -191,7 +190,7 @@ namespace Sarand.Net.Standard.Dbas.Data } catch (Exception exc) { - SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); + SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); SetErrorState(exc.Message); } return new DbDataTable(dataTable); @@ -200,9 +199,8 @@ namespace Sarand.Net.Standard.Dbas.Data /// Execute a non-query statement and return the row count. public override int ExecuteNonQuery(string query) { - String fn = GetType().Name + ".ExecuteNonQuery"; ClearErrorState(); - Log(fn, "Query: " + query, SaMessageLevel.Debug); + Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); using (var dbConnection = CreateConnection()) using (var dbCommand = dbConnection.CreateCommand()) { @@ -215,7 +213,7 @@ namespace Sarand.Net.Standard.Dbas.Data } catch (Exception exc) { - SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); + SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); SetErrorState(exc.Message); } } @@ -225,13 +223,12 @@ namespace Sarand.Net.Standard.Dbas.Data /// Compile and execute an insert statement and return the row count. public override int ExecuteInsert(String tableName, List columnNameValues) { - String fn = GetType().Name + ".ExecuteInsert"; ClearErrorState(); String query = "INSERT INTO " + tableName + " " + "(" + columnNameValues.GetValues(StringPair.Value.Value1).Concat(", ") + ") " + "VALUES (" + columnNameValues.GetValues(StringPair.Value.Value2).Concat(", ") + ")"; - Log(fn, "Query: " + query, SaMessageLevel.Debug); + Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); using (var dbConnection = CreateConnection()) using (var dbCommand = dbConnection.CreateCommand()) { @@ -244,7 +241,7 @@ namespace Sarand.Net.Standard.Dbas.Data } catch (Exception exc) { - SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); + SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); SetErrorState(exc.Message); } } @@ -254,14 +251,13 @@ namespace Sarand.Net.Standard.Dbas.Data /// Compile and execute an update statement and return the row count. public override int ExecuteUpdate(String tableName, List columnNameValues, String filter) { - String fn = GetType().Name + ".ExecuteUpdate"; ClearErrorState(); String query = "UPDATE " + tableName + " " + "SET " + columnNameValues.Concat("=").Concat(", "); if (!StringUtil.IsEmpty(filter)) query += (String)" WHERE " + filter; - Log(fn, "Query: " + query, SaMessageLevel.Debug); + Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); using (var dbConnection = CreateConnection()) using (var dbCommand = dbConnection.CreateCommand()) { @@ -274,7 +270,7 @@ namespace Sarand.Net.Standard.Dbas.Data } catch (Exception exc) { - SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); + SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); SetErrorState(exc.Message); } } @@ -284,12 +280,11 @@ namespace Sarand.Net.Standard.Dbas.Data /// Compile and execute a delete statement and return the row count. public override int ExecuteDelete(String tableName, String filter) { - String fn = GetType().Name + ".ExecuteDelete"; ClearErrorState(); String query = "DELETE FROM " + tableName; if (!StringUtil.IsEmpty(filter)) query += " WHERE " + filter; - Log(fn, "Query: " + query, SaMessageLevel.Debug); + Log(SysUtil.Fn, "Query: " + query, SaMessageLevel.Debug); using (var dbConnection = CreateConnection()) using (var dbCommand = dbConnection.CreateCommand()) { @@ -302,7 +297,7 @@ namespace Sarand.Net.Standard.Dbas.Data } catch (Exception exc) { - SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); + SaLogger.Error(SysUtil.Fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true); SetErrorState(exc.Message); } } @@ -449,8 +444,7 @@ namespace Sarand.Net.Standard.Dbas.Data { if (StringUtil.IsEmpty(tableNameRestriction) && tables.Count > 0) return; if (!StringUtil.IsEmpty(tableNameRestriction) && tables.Exists(item => item.Name.Equals(tableNameRestriction))) return; - String fn = GetType().Name + ".PopulateTableNames"; - if (SacsSystem.DebugMode) SaLogger.Debug(fn + (!StringUtil.IsEmpty(tableNameRestriction) ? " Table: " + tableNameRestriction : " ")); + if (SacsSystem.DebugMode) SaLogger.Debug(SysUtil.Fn + (!StringUtil.IsEmpty(tableNameRestriction) ? " Table: " + tableNameRestriction : " ")); String[] restrictions = new String[] { null, null, tableNameRestriction }; // database, owner, table name, type using (DataTable tableSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Tables, restrictions)) { @@ -490,7 +484,7 @@ namespace Sarand.Net.Standard.Dbas.Data else if (colName.EqualsCI("REMARKS")) remarks = value; else - SaLogger.Warn(fn + " Unrecognised column name: '" + colName + "'"); + SaLogger.Warn(SysUtil.Fn + " Unrecognised column name: '" + colName + "'"); } // TODO Why is Type sometimes "BASE TABLE"? if (!StringUtil.IsEmpty(tableName) && (type.EqualsCI("Table") || type.EqualsCI("Base Table")) && (!schema.EqualsCI("sys") || !tableName.Left(9).EqualsCI("trace_xe_"))) @@ -513,8 +507,7 @@ namespace Sarand.Net.Standard.Dbas.Data //------------------------------------------------------------------- private void PopulateTableColumns(DbConnection dbConnection, DbDfnTable table) { - String fn = GetType().Name + ".PopulateTableColumns"; - if (SacsSystem.DebugMode) SaLogger.Debug(fn + " Table: " + table.Name); + if (SacsSystem.DebugMode) SaLogger.Debug(SysUtil.Fn + " Table: " + table.Name); short columnPos = 0; String[] restrictions = new String[] { null, table.Schema, table.Name, null }; // database, owner, table name, column name using (DataTable columnSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Columns, restrictions)) @@ -718,7 +711,6 @@ namespace Sarand.Net.Standard.Dbas.Data //------------------------------------------------------------------- private void PopulateTableIndices(DbConnection dbConnection, DbDfnTable table) { - String fn = GetType().Name + ".PopulateTableIndices"; String[] restrictions = new String[] { null, table.Schema, table.Name }; // database, owner, table name using (DataTable columnSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Indexes, restrictions)) { diff --git a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDataItem_Db.cs b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDataItem_Db.cs index b66b953..407012c 100644 --- a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDataItem_Db.cs +++ b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceDataItem_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessDataItem_Db.cs b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessDataItem_Db.cs index 0369243..b8f4887 100644 --- a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessDataItem_Db.cs +++ b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessDataItem_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessMessage_Db.cs b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessMessage_Db.cs index 9edd79e..d04c182 100644 --- a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessMessage_Db.cs +++ b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcessMessage_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcess_Db.cs b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcess_Db.cs index 42199d9..5a2fb11 100644 --- a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcess_Db.cs +++ b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterfaceProcess_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterface_Db.cs b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterface_Db.cs index a92943f..b638a06 100644 --- a/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterface_Db.cs +++ b/SacsStandardDbas/Dbas/Interfaces/Internal/SaInterface_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDoc_Db.cs b/SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDoc_Db.cs index c215214..89c7d51 100644 --- a/SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDoc_Db.cs +++ b/SacsStandardDbas/Dbas/ItemDocs/Internal/SaItemDoc_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOption_Db.cs b/SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOption_Db.cs index f13d598..36b21ae 100644 --- a/SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOption_Db.cs +++ b/SacsStandardDbas/Dbas/ItemOptions/Internal/SaItemOption_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Proc/SaDbProcess_CompareDatabaseSchemas.cs b/SacsStandardDbas/Dbas/Proc/SaDbProcess_CompareDatabaseSchemas.cs index 88c9476..b92b1c7 100644 --- a/SacsStandardDbas/Dbas/Proc/SaDbProcess_CompareDatabaseSchemas.cs +++ b/SacsStandardDbas/Dbas/Proc/SaDbProcess_CompareDatabaseSchemas.cs @@ -41,10 +41,6 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Definitions. - public interface IProcessNotifier : ISacsProcessNotifier - { - void OnSchemaComparisonItemAdded(SaDbProcess_CompareDatabaseSchemas proc); - } public class SchemaComparisonItem { internal SchemaComparisonItem(String tableName, String columnName, int columnPosition, String indexName, String comment) @@ -68,7 +64,6 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Data members. private Config config; - private IProcessNotifier processNotifier = null; private readonly List schemaComparisonItems = new List(); //------------------------------------------------------------------- @@ -88,13 +83,19 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Properties. public override string ProcessDescription { get; } = "Database Schema Comparison"; - public new IProcessNotifier ProcessNotifier + + //------------------------------------------------------------------- + // Events. + + // Processing Completed. + public class SchemaComparisonItemAddedEventArgs : EventArgs { } + public event EventHandler OnSchemaComparisonItemAdded; + protected virtual void InvokeSchemaComparisonItemAdded() { - get { return processNotifier; } - set + if (OnSchemaComparisonItemAdded != null) { - processNotifier = value; - base.ProcessNotifier = value; + SchemaComparisonItemAddedEventArgs args = new SchemaComparisonItemAddedEventArgs(); + OnSchemaComparisonItemAdded(this, args); } } @@ -112,7 +113,7 @@ namespace Sarand.Net.Standard.Dbas.Proc protected override bool Run() { schemaComparisonItems.Clear(); - if (config.SourceDatabase == null && config.SourceTables == null) return SetProcessError("Source database must be supplied."); + if (config.SourceDatabase == null && config.SourceTables == null) return AddErrorMessage("Source database must be supplied."); CompareTables(); return OK; @@ -393,7 +394,7 @@ namespace Sarand.Net.Standard.Dbas.Proc private void AddSchemaComparisonItem(String tableName, String columnName, int position, String indexName, String comment) { schemaComparisonItems.Add(new SchemaComparisonItem(tableName, columnName, position, indexName, comment)); - if (ProcessNotifier != null) ProcessNotifier.OnSchemaComparisonItemAdded(this); + InvokeSchemaComparisonItemAdded(); } //------------------------------------------------------------------- diff --git a/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbDefinition.cs b/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbDefinition.cs index 4704f8c..1cfcb2e 100644 --- a/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbDefinition.cs +++ b/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbDefinition.cs @@ -32,7 +32,7 @@ namespace Sarand.Net.Standard.Dbas.Proc } public new class Config : SaProcess_Base.Config { - public Config () + public Config() { } public SourceDefinitionType SourceDefinitionType { get; set; } @@ -43,9 +43,6 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Definitions. - public interface IProcessNotifier : ISacsProcessNotifier - { - } private class Statistics { } @@ -53,17 +50,16 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Data members. private Config config; - private IProcessNotifier processNotifier; private readonly List tableDfns = new List(); private Statistics stats; //------------------------------------------------------------------- // Construction. - public SaDbProcess_ConvertDbDefinition (Config config) : base(config) + public SaDbProcess_ConvertDbDefinition(Config config) : base(config) { this.config = config; } - protected override void Dispose (bool disposing) + protected override void Dispose(bool disposing) { if (disposing) { @@ -74,15 +70,6 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // Properties. public override string ProcessDescription { get; } = "Database Definition Convertor"; - public new IProcessNotifier ProcessNotifier - { - get { return processNotifier; } - set - { - processNotifier = value; - base.ProcessNotifier = value; - } - } /// Processing statistics. public override List ProcessingStatistics { @@ -102,7 +89,7 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // P r o c e s s i n g //------------------------------------------------------------------- - protected override bool Run () + protected override bool Run() { // Initialise process. tableDfns.Clear(); @@ -115,32 +102,32 @@ namespace Sarand.Net.Standard.Dbas.Proc return OK; } - protected bool ValidateConfiguration () + protected bool ValidateConfiguration() { // Source file. if (config.SourceDefinitionType <= 0) - return SetProcessError("Source definition type must be specified."); + return AddErrorMessage("Source definition type must be specified."); if (StringUtil.IsEmpty(config.SourceDefinitionFilePath)) - return SetProcessError("Source definition file must be specified."); + return AddErrorMessage("Source definition file must be specified."); if (!FileUtil.IsFile(config.SourceDefinitionFilePath)) - return SetProcessError("Source definition file not found."); + return AddErrorMessage("Source definition file not found."); // Target file. if (config.TargetDefinitionType <= 0) - return SetProcessError("Target definition type must be specified."); + return AddErrorMessage("Target definition type must be specified."); if (StringUtil.IsEmpty(config.TargetDefinitionFilePath)) - return SetProcessError("Target definition file must be specified."); + return AddErrorMessage("Target definition file must be specified."); if (!DirectoryUtil.DirectoryExists(DirectoryUtil.GetDirectoryName(config.TargetDefinitionFilePath))) - return SetProcessError("Target folder not found."); + return AddErrorMessage("Target folder not found."); if (config.SourceDefinitionFilePath.EqualsCI(config.TargetDefinitionFilePath)) - return SetProcessError("Target file must not be the same as the source file."); + return AddErrorMessage("Target file must not be the same as the source file."); return OK; } //------------------------------------------------------------------- - private bool ConvertCppDatabaseDefinition () + private bool ConvertCppDatabaseDefinition() { if (!OK) return false; @@ -169,7 +156,7 @@ namespace Sarand.Net.Standard.Dbas.Proc } private class SourceDefinition_Params { - protected SourceDefinition_Params () { } + protected SourceDefinition_Params() { } public int LineNumber { get; set; } = -1; public int TableIdx { get; set; } = -1; public int ColumnIdx { get; set; } = -1; @@ -186,7 +173,7 @@ namespace Sarand.Net.Standard.Dbas.Proc public Dictionary ColumnMap { get; } = new Dictionary(); } //------------------------------------------------------------------- - private bool ExtractDatabaseDefinition () + private bool ExtractDatabaseDefinition() { if (!OK) return false; @@ -245,7 +232,7 @@ namespace Sarand.Net.Standard.Dbas.Proc file.Close(); } } - catch (Exception exc) { SaLogger.Warn(ExceptionUtil.GetLogMessage(exc)); SetProcessError(ExceptionUtil.GetErrorStateMessage(exc)); } + catch (Exception exc) { SaLogger.Warn(ExceptionUtil.GetLogMessage(exc)); AddErrorMessage(ExceptionUtil.GetErrorStateMessage(exc)); } return OK; } @@ -253,7 +240,7 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- // C + + C o d e S o u r c e //------------------------------------------------------------------- - private readonly Dictionary cppColumnDataTypes = new Dictionary() + private readonly Dictionary cppColumnDataTypes = new Dictionary() { { 1, "VarChar" }, { -7, "Bit" }, @@ -265,7 +252,7 @@ namespace Sarand.Net.Standard.Dbas.Proc { 8, "Double" }, { 93, "DateTime" } }; - private bool ExtractDatabaseDefinitionLine_CppCode (SourceDefinition_Params_CppCode sourceDfnParams, String line) + private bool ExtractDatabaseDefinitionLine_CppCode(SourceDefinition_Params_CppCode sourceDfnParams, String line) { if (!OK) return false; @@ -276,7 +263,7 @@ namespace Sarand.Net.Standard.Dbas.Proc { // Table enum start. if (sourceDfnParams.TablesEnumFound) - return SetProcessError("Found multiple table enums at line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Found multiple table enums at line " + sourceDfnParams.LineNumber + ": " + line + "."); sourceDfnParams.TablesEnumFound = true; sourceDfnParams.InTablesEnum = true; return OK; @@ -289,13 +276,13 @@ namespace Sarand.Net.Standard.Dbas.Proc else if (!line.ContainsCI("eSaDbTblUnknown")) { if (!line.ContainsCI("eSaDbTbl")) - return SetProcessError("Invalid table definition on line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Invalid table definition on line " + sourceDfnParams.LineNumber + ": " + line + "."); // Extract table name. - String tableName = line.ReplaceAll("eSaDbTbl","").ReplaceAll(",",""); + String tableName = line.ReplaceAll("eSaDbTbl", "").ReplaceAll(",", ""); if (StringUtil.IsEmpty(tableName)) - return SetProcessError("Blank table name on line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Blank table name on line " + sourceDfnParams.LineNumber + ": " + line + "."); if (tableDfns.Exists(item => item.Name.EqualsCI(tableName))) - return SetProcessError("Duplicate table name on line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Duplicate table name on line " + sourceDfnParams.LineNumber + ": " + line + "."); // Add this table to the list. sourceDfnParams.TableIdx++; //SaLogger.Debug("Table #" + sourceDfnParams.TableIdx + ": " + tableName); @@ -310,7 +297,7 @@ namespace Sarand.Net.Standard.Dbas.Proc { // Column enum start. if (sourceDfnParams.ColumnsEnumFound) - return SetProcessError("Found multiple columns enums at line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Found multiple columns enums at line " + sourceDfnParams.LineNumber + ": " + line + "."); sourceDfnParams.ColumnsEnumFound = true; sourceDfnParams.InColumnsEnum = true; return OK; @@ -323,7 +310,7 @@ namespace Sarand.Net.Standard.Dbas.Proc else if (!line.ContainsCI("eSaDbColUnknown")) { if (!line.ContainsCI("eSaDbCol")) - return SetProcessError("Invalid column definition on line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Invalid column definition on line " + sourceDfnParams.LineNumber + ": " + line + "."); // Column definitions: // eSaDbColUnknown = -1, // #define eSaDbColVendorType_ListID (eSaDbColumn)4101 @@ -345,7 +332,7 @@ namespace Sarand.Net.Standard.Dbas.Proc // Split table and column names on Underscore. String[] tableColumnNameParts = tableColumnName.Split('_'); if (tableColumnNameParts.Length != 2) - return SetProcessError("Invalid column definition on line (missing underscore?) " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Invalid column definition on line (missing underscore?) " + sourceDfnParams.LineNumber + ": " + line + "."); sourceDfnParams.ColumnIdx++; String tableName = tableColumnNameParts[0]; String columnName = tableColumnNameParts[1]; @@ -360,7 +347,7 @@ namespace Sarand.Net.Standard.Dbas.Proc { // Column definitions start. if (sourceDfnParams.ColumnsDfnsFound) - return SetProcessError("Found multiple column definitions at line " + sourceDfnParams.LineNumber + ": " + line + "."); + return AddErrorMessage("Found multiple column definitions at line " + sourceDfnParams.LineNumber + ": " + line + "."); sourceDfnParams.ColumnsDfnsFound = true; sourceDfnParams.InColumnsDfns = true; sourceDfnParams.ColumnIdx = -1; @@ -388,40 +375,40 @@ namespace Sarand.Net.Standard.Dbas.Proc // eTime = 93 SQL_C_TYPE_TIMESTAMP foreach (String columnDfnDataRaw in line.Split(new String[] { "}," }, StringSplitOptions.RemoveEmptyEntries)) { - String columnDfnData = columnDfnDataRaw.ReplaceAll("{","").TrimAll(); + String columnDfnData = columnDfnDataRaw.ReplaceAll("{", "").TrimAll(); //SaLogger.Debug("Column dfn: " + columnDfnData); String[] columnDfnParts = columnDfnData.Split(','); - if (columnDfnParts.Length != 4) return SetProcessError("Invalid column definition (expecting 4 parts) at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (columnDfnParts.Length != 4) return AddErrorMessage("Invalid column definition (expecting 4 parts) at line " + sourceDfnParams.LineNumber + ": " + line + "."); String tableIdxRaw = columnDfnParts[0].TrimAll(); int tableIdx = StringUtil.ToInt(tableIdxRaw); - String columnName = columnDfnParts[1].TrimAll().ReplaceAll("\"",""); + String columnName = columnDfnParts[1].TrimAll().ReplaceAll("\"", ""); String dataTypeRaw = columnDfnParts[2].TrimAll(); int dataType = StringUtil.ToInt(dataTypeRaw); String columnLengthRaw = columnDfnParts[3].TrimAll(); int columnLength = StringUtil.ToInt(columnLengthRaw); - if (!tableIdxRaw.IsNumeric()) return SetProcessError("Invalid table index in column definition (" + tableIdxRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); - if (StringUtil.IsEmpty(columnName)) return SetProcessError("Blank name in column definition at line " + sourceDfnParams.LineNumber + ": " + line + "."); - if (!dataTypeRaw.IsNumeric(true) || !cppColumnDataTypes.ContainsKey(dataType)) return SetProcessError("Invalid data type in column definition (" + dataTypeRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); - if (!columnLengthRaw.IsNumeric()) return SetProcessError("Invalid column length in column definition (" + columnLengthRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (!tableIdxRaw.IsNumeric()) return AddErrorMessage("Invalid table index in column definition (" + tableIdxRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (StringUtil.IsEmpty(columnName)) return AddErrorMessage("Blank name in column definition at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (!dataTypeRaw.IsNumeric(true) || !cppColumnDataTypes.ContainsKey(dataType)) return AddErrorMessage("Invalid data type in column definition (" + dataTypeRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (!columnLengthRaw.IsNumeric()) return AddErrorMessage("Invalid column length in column definition (" + columnLengthRaw + ") at line " + sourceDfnParams.LineNumber + ": " + line + "."); // Table definition. String tableName = sourceDfnParams.TableMap.Values.ToList().Find(item => item.TableIdx == tableIdx)?.TableName ?? ""; - if (StringUtil.IsEmpty(tableName)) return SetProcessError("Unable to identify table name for Table idx " + tableIdx + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (StringUtil.IsEmpty(tableName)) return AddErrorMessage("Unable to identify table name for Table idx " + tableIdx + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); DbDfnTable tableDfn = tableDfns.GetTableByName(tableName); - if (tableDfn == null) return SetProcessError("Unable to retrieve table definition for Table name " + tableName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (tableDfn == null) return AddErrorMessage("Unable to retrieve table definition for Table name " + tableName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); // Column data. sourceDfnParams.ColumnIdx++; ColumnData columnData = sourceDfnParams.ColumnMap.Values.ToList().Find(item => item.TableName.EqualsCI(tableName) && item.ColumnIdx == sourceDfnParams.ColumnIdx); - if (columnData == null) return SetProcessError("Unable to column data for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); - if (columnData.ColumnName.EqualsCI(columnName)) return SetProcessError("Inconsistent column name for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (columnData == null) return AddErrorMessage("Unable to column data for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (columnData.ColumnName.EqualsCI(columnName)) return AddErrorMessage("Inconsistent column name for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); // Column definition. DbDfnColumn columnDfn = new DbDfnColumn() { Table = tableName, Name = columnName, - DataType = EnumUtil.ParseEnum(cppColumnDataTypes.GetValue(dataType,""),DbColumnDataType.Unknown), + DataType = EnumUtil.ParseEnum(cppColumnDataTypes.GetValue(dataType, ""), DbColumnDataType.Unknown), Size = columnLength }; - if (columnDfn.DataType < 0) return SetProcessError("Invalid data type for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); + if (columnDfn.DataType < 0) return AddErrorMessage("Invalid data type for table " + tableName + " and column " + columnName + " at line " + sourceDfnParams.LineNumber + ": " + line + "."); tableDfn.AddColumn(columnDfn); } return OK; diff --git a/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbSchema.cs b/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbSchema.cs index c23509d..92eb71d 100644 --- a/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbSchema.cs +++ b/SacsStandardDbas/Dbas/Proc/SaDbProcess_ConvertDbSchema.cs @@ -62,7 +62,7 @@ namespace Sarand.Net.Standard.Dbas.Proc //------------------------------------------------------------------- public List ConvertXmlToTables(String xmlSchemaData) { - ClearProcessError(); + ClearProcessMessages(); String fn = GetType().Name + ".ConvertXmlToTables"; Dictionary tables = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -169,7 +169,7 @@ namespace Sarand.Net.Standard.Dbas.Proc } catch (Exception exc) { - SetProcessError(exc.GetErrorStateMessage()); + AddErrorMessage(exc.GetErrorStateMessage()); SaLogger.Warn(fn + " " + ExceptionUtil.GetLogMessage(exc)); } diff --git a/SacsStandardDbas/Dbas/ReferenceData/Internal/Language_Db.cs b/SacsStandardDbas/Dbas/ReferenceData/Internal/Language_Db.cs index 5c64153..a197ee1 100644 --- a/SacsStandardDbas/Dbas/ReferenceData/Internal/Language_Db.cs +++ b/SacsStandardDbas/Dbas/ReferenceData/Internal/Language_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/ReferenceData/Internal/Region_Db.cs b/SacsStandardDbas/Dbas/ReferenceData/Internal/Region_Db.cs index 02524b9..f0ab6d0 100644 --- a/SacsStandardDbas/Dbas/ReferenceData/Internal/Region_Db.cs +++ b/SacsStandardDbas/Dbas/ReferenceData/Internal/Region_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/ReferenceData/Internal/TaxCode_Db.cs b/SacsStandardDbas/Dbas/ReferenceData/Internal/TaxCode_Db.cs index f1af50e..b8d5bd4 100644 --- a/SacsStandardDbas/Dbas/ReferenceData/Internal/TaxCode_Db.cs +++ b/SacsStandardDbas/Dbas/ReferenceData/Internal/TaxCode_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/ReferenceData/Internal/UnitMeasure_Db.cs b/SacsStandardDbas/Dbas/ReferenceData/Internal/UnitMeasure_Db.cs index 1653c03..184b370 100644 --- a/SacsStandardDbas/Dbas/ReferenceData/Internal/UnitMeasure_Db.cs +++ b/SacsStandardDbas/Dbas/ReferenceData/Internal/UnitMeasure_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Security/Internal/SaSecItem_Db.cs b/SacsStandardDbas/Dbas/Security/Internal/SaSecItem_Db.cs index 750d2c4..9fd01d4 100644 --- a/SacsStandardDbas/Dbas/Security/Internal/SaSecItem_Db.cs +++ b/SacsStandardDbas/Dbas/Security/Internal/SaSecItem_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/Security/Internal/SaSecLink_Db.cs b/SacsStandardDbas/Dbas/Security/Internal/SaSecLink_Db.cs index 4811f48..ab7eb5c 100644 --- a/SacsStandardDbas/Dbas/Security/Internal/SaSecLink_Db.cs +++ b/SacsStandardDbas/Dbas/Security/Internal/SaSecLink_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/Dbas/UpdateLog/Internal/SaAttrUpdate_Db.cs b/SacsStandardDbas/Dbas/UpdateLog/Internal/SaAttrUpdate_Db.cs index f38729f..7b677ae 100644 --- a/SacsStandardDbas/Dbas/UpdateLog/Internal/SaAttrUpdate_Db.cs +++ b/SacsStandardDbas/Dbas/UpdateLog/Internal/SaAttrUpdate_Db.cs @@ -3,7 +3,6 @@ using Sarand.Net.Standard.Dbas.Data; using System; using System.Collections.Generic; using System.Data.Common; -using System.Data.Odbc; using Microsoft.Data.SqlClient; using System.Linq; using Sarand.Net.Standard.Base.Dbas; diff --git a/SacsStandardDbas/SacsStandardDbas.csproj b/SacsStandardDbas/SacsStandardDbas.csproj index 6a4c1e5..db175c4 100644 --- a/SacsStandardDbas/SacsStandardDbas.csproj +++ b/SacsStandardDbas/SacsStandardDbas.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 Sarand.Net.Standard @@ -12,7 +12,7 @@ - + diff --git a/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs b/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs index 631daee..c87c97c 100644 --- a/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs +++ b/SacsStandardMob/Mobile/Forms/Layouts/SaxGrid.cs @@ -54,6 +54,7 @@ namespace Sarand.Net.Standard.Mobile.Forms.Templates { base.OnRemoved(view); } + [Obsolete("OnChildRemoved(Element) is obsolete as of version 4.8.0. Please use OnChildRemoved(Element, int) instead.")] protected override void OnChildRemoved (Element child) { base.OnChildRemoved(child); diff --git a/SacsStandardMob/Mobile/Forms/Panels/SaxPanel.cs b/SacsStandardMob/Mobile/Forms/Panels/SaxPanel.cs index 6bb7cd0..63a01a2 100644 --- a/SacsStandardMob/Mobile/Forms/Panels/SaxPanel.cs +++ b/SacsStandardMob/Mobile/Forms/Panels/SaxPanel.cs @@ -66,6 +66,7 @@ namespace Sarand.Net.Standard.Mobile.Forms.Panels { base.OnChildAdded(child); } + [Obsolete("OnChildRemoved(Element) is obsolete as of version 4.8.0. Please use OnChildRemoved(Element, int) instead.")] protected override void OnChildRemoved (Element child) { base.OnChildRemoved(child); diff --git a/SacsStandardMob/SacsStandardMob.csproj b/SacsStandardMob/SacsStandardMob.csproj index 0d26035..82045e8 100644 --- a/SacsStandardMob/SacsStandardMob.csproj +++ b/SacsStandardMob/SacsStandardMob.csproj @@ -1,13 +1,13 @@ - netstandard2.1 + net6.0 Sarand.Net.Standard - - + + diff --git a/SacsStandardProc/Proc/Excel/ExcelHelper.cs b/SacsStandardProc/Proc/Excel/ExcelHelper.cs new file mode 100644 index 0000000..45dddfb --- /dev/null +++ b/SacsStandardProc/Proc/Excel/ExcelHelper.cs @@ -0,0 +1,38 @@ +using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; + +namespace Sarand.Net.Standard.Proc.Excel +{ + //======================================================================= + public static class ExcelHelper + { + //------------------------------------------------------------------- + /// + /// Validate worksheet name. + /// Worksheet names cannot: + /// - Be blank. + /// - Contain more than 31 characters. + /// - Contain any of the following characters: / \ ? * : [ ] + /// - For example, 02/17/2016 would not be a valid worksheet name, but 02-17-2016 would work fine. + /// - Begin or end with an apostrophe ('), but they can be used in between text or numbers in a name. + /// - Be named "History". This is a reserved word Excel uses internally. + /// + /// + /// + public static bool IsValidSheetName(String name) + { + if (Base.Utils.StringUtil.IsBlank(name)) return false; + if (name.Length > 31) return false; + if (Regex.IsMatch(name, "[/\\\\?*:[\\]]")) return false; + if (name.StartsWith("'") || name.EndsWith("'")) return false; + if (name.EqualsCI("History")) return false; + return true; + } + + //------------------------------------------------------------------- + } + //======================================================================= +} diff --git a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet-Dfn.cs b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet-Dfn.cs index dfde595..1d7325d 100644 --- a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet-Dfn.cs +++ b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet-Dfn.cs @@ -140,7 +140,7 @@ namespace Sarand.Net.Standard.Proc.Excel Yellow = 6, Pink = 7, Magenta = 7, - Turqoise = 8, + Turquoise = 8, Cyan = 8, DarkRed = 9, Green = 10, @@ -151,7 +151,7 @@ namespace Sarand.Net.Standard.Proc.Excel Gray25 = 15, Gray50 = 16, SkyBlue = 33, - LightTurqoise = 34, + LightTurquoise = 34, LightGreen = 35, LightYellow = 36, PaleBlue = 37, diff --git a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet.cs b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet.cs index cf64316..7738189 100644 --- a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet.cs +++ b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet.cs @@ -4,6 +4,10 @@ using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; +using DocumentFormat.OpenXml.Spreadsheet; +using NPOI.SS.UserModel; +using NPOI.SS.Formula.Functions; +using System.Text.RegularExpressions; namespace Sarand.Net.Standard.Proc.Excel { @@ -16,24 +20,24 @@ namespace Sarand.Net.Standard.Proc.Excel //======================================================================= public class SaMsExcelRange { - public uint ColFrom { get; set; } = 0; - public uint ColTo { get; set; } = 0; - public uint RowFrom { get; set; } = 0; - public uint RowTo { get; set; } = 0; + public uint ColFromPos { get; set; } = 0; + public uint ColToPos { get; set; } = 0; + public uint RowFromPos { get; set; } = 0; + public uint RowToPos { get; set; } = 0; public SaMsExcelRange() { } public SaMsExcelRange(uint colPos, uint rowPos) { - ColFrom = colPos; - ColTo = colPos; - RowFrom = rowPos; - RowTo = rowPos; + ColFromPos = colPos; + ColToPos = colPos; + RowFromPos = rowPos; + RowToPos = rowPos; } public SaMsExcelRange(uint colPosFrom, uint rowPosFrom, uint colPosTo, uint rowPosTo) { - ColFrom = colPosFrom; - ColTo = colPosTo; - RowFrom = rowPosFrom; - RowTo = rowPosTo; + ColFromPos = colPosFrom; + ColToPos = colPosTo; + RowFromPos = rowPosFrom; + RowToPos = rowPosTo; } } //======================================================================= @@ -78,17 +82,29 @@ namespace Sarand.Net.Standard.Proc.Excel //======================================================================= public class SaMsExcelColumnHeading_Config { - public double ColumnWidthPixelsPerCm { get; set; } = 43.0; // Between 10 and 100 - public uint FirstHeadingRow { get; set; } = 1; // Must be > 0 - public uint FirstHeadingColumn { get; set; } = 1; // Must be > 0 - public uint FreezeColumn { get; set; } = 0; // 0 = don't freeze - public short HeadingFontSize { get; set; } = 9; - public bool HeadingTextBold { get; set; } = true; - public bool HeadingTextWrap { get; set; } = true; - /// Cell font colour index, ref. XlColorIndex - public SaXlColourIndex InteriorColourIndex { get; set; } = SaXlColourIndex.NotSpecified; - /// Cell RGB colour value, ref. XlRgbColor. Overrides colour index if changed from default value. - public int? InteriorColour { get; set; } = null; + // Heading configuration. + /// Pixels per cm. Default 43. Min 10, max 100. + public double ColumnWidthPixelsPerCm { get; set; } = 43.0; + /// First heading column position (from 1). Default 1. Must be > 0. + public uint FirstHeadingColPos { get; set; } = 1; + /// First heading row position (from 1). Default 1. Must be > 0. + public uint FirstHeadingRowPos { get; set; } = 1; + /// First data row position. Reset by SetColumnHeadings(). + public uint FirstDataRowPos { get; set; } = 2; + /// Freeze column position - cell in first data row. 0 = don't freeze. + public uint FreezeColPos { get; set; } = 0; + /// Font attributes. Default Arial 9 bold. + public SaMsExcelFontAttr FontAttr { get; } = new SaMsExcelFontAttr() { Name = "Arial", HeightInPoints = 9, Bold = true }; + /// Whether to wrap column header cells. + public bool HeadingTextWrap { get; set; } = false; + /// Cell vertical alignment. XlVAlign.xlTop, xlCenter, xlBottom, xlJustify, xlDistributed etc. + public short? VerticalAlignment { get; set; } = null; + /// Cell foreground colour index, ref. XlColorIndex + public SaXlColourIndex ForegroundColourIndex { get; set; } = SaXlColourIndex.NotSpecified; + /// Cell foreground RGB colour value, ref. XlRgbColor. Overrides foreground colour index if changed from default value. + public int? ForegroundColourRGB { get; set; } = null; + /// Cell background colour index, ref. XlColorIndex + public SaXlColourIndex BackgroundColourIndex { get; set; } = SaXlColourIndex.NotSpecified; public SaXlBorderWeight BorderWeight { get; set; } = SaXlBorderWeight.NotSpecified; public bool SetColumnWidths { get; set; } = true; public bool SetColumnAlignment { get; set; } = true; @@ -147,29 +163,55 @@ namespace Sarand.Net.Standard.Proc.Excel public class SaMsExcelFontAttr { /// Font name. Arial etc. - public String FontName { get; set; } = String.Empty; + public String Name { get; set; } = String.Empty; /// Font style. Regular etc. public String FontStyle { get; set; } = String.Empty; - public short? FontSize { get; set; } = null; + public double? HeightInPoints { get; set; } = null; /// Font colour index, ref. XlColorIndex. - public SaXlColourIndex FontColourIndex { get; set; } = SaXlColourIndex.NotSpecified; - /// Font RGB colour value, ref. XlRgbColor. Overrides colour index if changed from default value. - public int? FontColor { get; set; } = null; + public SaXlColourIndex ColourIndex { get; set; } = SaXlColourIndex.NotSpecified; + /// Font RGB colour value, ref. XlRgbColor. + public int? ColourRGB { get; set; } = null; public bool? Bold { get; set; } = null; public bool? Strikethrough { get; set; } = null; public bool? Superscript { get; set; } = null; public bool? Subscript { get; set; } = null; - public bool? OutlineFont { get; set; } = null; + public bool? Outline { get; set; } = null; public bool? Shadow { get; set; } = null; public SaXlUnderlineStyle? Underline { get; set; } = null; + public void CopyFrom(SaMsExcelFontAttr fontAttr) + { + Name = fontAttr.Name; + FontStyle = fontAttr.FontStyle; + HeightInPoints = fontAttr.HeightInPoints; + ColourIndex = fontAttr.ColourIndex; + ColourRGB = fontAttr.ColourRGB; + Bold = fontAttr.Bold; + Strikethrough = fontAttr.Strikethrough; + Superscript = fontAttr.Superscript; + Subscript = fontAttr.Subscript; + Outline = fontAttr.Outline; + Shadow = fontAttr.Shadow; + Underline = fontAttr.Underline; + } + public bool IsSpecified() + { + if (HeightInPoints != null && HeightInPoints >= SaMsSpreadsheet.FontHeightInPointsMin && HeightInPoints <= SaMsSpreadsheet.FontHeightInPointsMax) return true; + if (ColourRGB != null || ColourIndex != SaXlColourIndex.NotSpecified) return true; + if ((Bold ?? false) || (Strikethrough ?? false) || (Superscript ?? false) || (Subscript ?? false) || (Outline ?? false) || (Shadow ?? false) || (Underline != null)) return true; + return false; + } } //======================================================================= - public class SaMsExcelItemAttr : SaMsExcelFontAttr + /// + /// Style attributes. + /// + public class SaMsExcelStyleAttr { // Item attributes. + public SaMsExcelFontAttr FontAttr { get; } = new SaMsExcelFontAttr(); // Attributes that are changed from their default (generally null) are set against the specified range. /// Cell data format. "@"=Text etc. - public String NumberFormat { get; set; } = String.Empty; + public String DataFormat { get; set; } = String.Empty; /// Cell horizontal alignment. XlHAlign.xlHAlignLeft, xlHAlignCenter, xlHAlignRight etc. public short? HorizontalAlignment { get; set; } = null; /// Cell indentation. Tabbed - left, right or distributed alignment [0 to 6] @@ -180,16 +222,16 @@ namespace Sarand.Net.Standard.Proc.Excel public bool? WrapText { get; set; } = null; /// Whether cells are merged. public bool? MergeCells { get; set; } = null; - /// Font colour index, ref. XlColorIndex. Black = 1 etc. - public SaXlColourIndex InteriorColourIndex { get; set; } = SaXlColourIndex.NotSpecified; - /// Cell RGB colour value, ref. XlRgbColor. Overrides colour index if changed from default value. - public int? InteriorColour { get; set; } = null; - /// Cell pattern, ref. XlPattern. xlSolid etc. + /// Font pattern foreground colour index, ref. XlColorIndex. Black = 1 etc. + public SaXlColourIndex ForegroundColourIndex { get; set; } = SaXlColourIndex.NotSpecified; + /// Cell pattern foreground RGB colour value, ref. XlRgbColor. Overrides colour index if changed from default value. + public int? ForegroundColourRGB { get; set; } = null; + /// Cell pattern, ref. XlPattern. xlSolid etc. NB Defaults to Solid if foreground colour specified. public SaXlPattern InteriorPattern { get; set; } = SaXlPattern.NotSpecified; - /// Cell pattern colour index, ref. XlColorIndex. xlAutomatic etc. - public SaXlColourIndex InteriorPatternColourIndex { get; set; } = SaXlColourIndex.NotSpecified; - /// Cell pattern RBG Colour value, ref. XlRgbColor. Overrides colour index if changed from default value. - public int? InteriorPatternColour { get; set; } = null; + /// Cell pattern background colour index, ref. XlColorIndex. xlAutomatic etc. + public SaXlColourIndex BackgroundColourIndex { get; set; } = SaXlColourIndex.NotSpecified; + /// Cell pattern background RBG Colour value, ref. XlRgbColor. Overrides colour index if changed from default value. + public int? BackgroundPatternColour { get; set; } = null; // Borders. public SaMsExcelBorderAttr BorderLeft { get; } = new SaMsExcelBorderAttr(XlBordersIndex.xlEdgeLeft); public SaMsExcelBorderAttr BorderTop { get; } = new SaMsExcelBorderAttr(XlBordersIndex.xlEdgeTop); @@ -201,12 +243,7 @@ namespace Sarand.Net.Standard.Proc.Excel { get { - List borders = new List(); - borders.Add(BorderLeft); - borders.Add(BorderTop); - borders.Add(BorderRight); - borders.Add(BorderBottom); - return borders; + return new List { BorderLeft, BorderTop, BorderRight, BorderBottom }; } } } @@ -215,9 +252,12 @@ namespace Sarand.Net.Standard.Proc.Excel { //------------------------------------------------------------------- // Definitions. - public static int FontSizeMin { get; } = 4; - public static int FontSizeMax { get; } = 48; + public static double FontHeightInPointsMin { get; } = 4; + public static double FontHeightInPointsMax { get; } = 48; public static int NullColour { get; } = 0x4a4d4120; /* AFX_RFX_LONG_PSEUDO_NULL */ + public const String Msg_NotSupported = "Not Supported"; + public const String Msg_WorkbookNotOpen = "Workbook is not open"; + public const String Msg_WorksheetNotOpen = "Worksheet is not open"; //------------------------------------------------------------------- // Data members. @@ -235,37 +275,55 @@ namespace Sarand.Net.Standard.Proc.Excel } //------------------------------------------------------------------- - // W o r k b o o k - //------------------------------------------------------------------- + // Workbook. + public abstract bool IsWorkbookValid(); public abstract bool CreateWorkbook(); public abstract bool OpenWorkbook(String filePath); + /// Cleanse workbook - remove unreferenced styles etc. + public abstract bool CleanseWorkbook(); public abstract bool SaveDocument(String filePath, int fileFormat = (int)SaXlFileFormat.Default /* Default: XlFileFormat.xlOpenXMLWorkbook */, bool bAutomaticallyOverwrite = false); public abstract bool CloseWorkbook(); + public abstract byte[] GetDocumentData(); + // Default style. + public abstract void ApplyDefaultStyle(SaMsExcelStyleAttr attr); //------------------------------------------------------------------- - // W o r k s h e e t - //------------------------------------------------------------------- - /// Is the current worksheet valid. - public abstract bool IsWorksheetValid(); + // Worksheets. + /// Number of worksheets in the workbook. public abstract uint WorksheetCount { get; } /// Set the worksheets in the workbook - sheets are added or removed to match the value. public abstract bool SetWorksheetCount(int worksheetCount); + /// Names of worksheets in the workbook. + public abstract List WorksheetNames { get; } + /// Add a worksheet to the workbook. public abstract bool AddWorksheet(String name); /// Remove the nth worksheet - position from 1. public abstract bool RemoveWorksheet(uint worksheetPos); + + //------------------------------------------------------------------- + // Worksheet. + /// Open the nth worksheet - position from 1. public abstract bool OpenWorksheet(uint worksheetPos); + /// Open the worksheet with the specified name. + public abstract bool OpenWorksheet(String name); + /// Close the current worksheet. + public abstract bool CloseWorksheet(); + + /// Is the current worksheet valid. + public abstract bool IsWorksheetValid(); + /// Get the position of the current worksheet. 0 = not open. public abstract uint WorksheetPos { get; } /// Get the name of the current worksheet. public abstract String WorksheetName { get; set; } + /// Set the default attributes for the entire worksheet - public abstract bool SetWorksheetAttr(SaMsExcelItemAttr attr); - /// Close the current worksheet. - public abstract bool CloseWorksheet(); + public abstract bool SetWorksheetAttr(SaMsExcelStyleAttr attr); + // Worksheet Page Setup. /// Configure the Page Setup properties. public abstract bool SetPageSetup(SaMsExcelPageSetup_Config pageSetupConfig); @@ -273,110 +331,175 @@ namespace Sarand.Net.Standard.Proc.Excel public abstract bool SetPageBreak(uint colPos, uint rowPos); //------------------------------------------------------------------- - // C o l u m n s - //------------------------------------------------------------------- - public abstract uint GetActiveColPos(); + // Columns. + + public abstract uint ActiveColPos { get; } public abstract double GetColWidth(uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point); public abstract bool SetColWidth(uint colPos, double fWidth, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point); //------------------------------------------------------------------- - // R o w s - //------------------------------------------------------------------- - public abstract uint GetActiveRowPos(); - public abstract List GetRowDataValues(uint rowPos, int maxColumns = 0); + // Rows. + + public abstract uint FirstRowPos { get; } + public abstract uint LastRowPos { get; } + public abstract uint ActiveRowPos { get; } + public abstract List GetRowValues(uint rowPos, int maxColumns = 0); public abstract double GetRowHeight(uint rowPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point); public abstract bool SetRowHeight(uint rowPos, double fHeight, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point); //------------------------------------------------------------------- - // C e l l s - //------------------------------------------------------------------- + // Cells. + public abstract void SetActiveCell(uint colPos, uint rowPos); + public abstract String GetCellFormula(uint colPos, uint rowPos); - public abstract DataValue GetCellDataValue(uint colPos, uint rowPos); - public abstract bool SetCellFormula(uint colPos, uint rowPos, String value); - public abstract bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelItemAttr attr); - public abstract bool SetRowFormulas(uint colStart, uint row, List values); - public abstract bool SetRangeFormulas(SaMsExcelRange range, List values); + public abstract String GetCellText(uint colPos, uint rowPos, bool trimWhitespace = true); + public abstract DataValue GetCellValue(uint colPos, uint rowPos); + + public abstract bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelStyleAttr attr = null); + public abstract bool SetCellValue(uint colPos, uint rowPos, DataValue value, SaMsExcelStyleAttr attr = null); + + /// Set cell formulas across a row - override if more efficient method available. + public virtual bool SetRowFormulas(uint colPosStart, uint rowPos, List values) + { + for (uint colPos = Math.Max(1, colPosStart); colPos <= values.Count; colPos++) + SetCellFormula(colPos, rowPos, values[(int)(colPos - 1)]); + return !HasErrorState(); + } + /// Set cell values across a row - override if more efficient method available. + public virtual bool SetRowValues(uint colPosStart, uint rowPos, List values) + { + for (uint colPos = Math.Max(1, colPosStart); colPos <= values.Count; colPos++) + SetCellValue(colPos, rowPos, values[(int)(colPos - 1)]); + return !HasErrorState(); + } + + /// + /// Set cell formulas across a range of cells - override if more efficient logic available. + /// - Values will wrap if more cells to fill than values. + public virtual bool SetRangeFormulas(SaMsExcelRange range, List values) + { + if (values.IsEmpty()) return SetErrorState(SysUtil.Fn + " No values supplied"); + int valueIdx = 0; + for (uint colPos = Math.Max(1, range.ColFromPos); colPos <= range.ColToPos; colPos++) + { + for (uint rowPos = Math.Max(1, range.RowFromPos); rowPos <= range.RowToPos; rowPos++) + { + // Wrap values if more cells to fill. + if (valueIdx >= values.Count) + valueIdx = 0; + SetCellFormula(colPos, rowPos, values[valueIdx++]); + } + } + return !HasErrorState(); + } + /// + /// Set cell values across a range of cells - override if more efficient logic available. + /// - Values will wrap if more cells to fill than values. + public virtual bool SetRangeValues(SaMsExcelRange range, List values) + { + if (values.IsEmpty()) return SetErrorState(SysUtil.Fn + " No values supplied"); + int valueIdx = 0; + for (uint colPos = Math.Max(1, range.ColFromPos); colPos <= range.ColToPos; colPos++) + { + for (uint rowPos = Math.Max(1, range.RowFromPos); rowPos <= range.RowToPos; rowPos++) + { + // Wrap values if more cells to fill. + if (valueIdx >= values.Count) + valueIdx = 0; + SetCellValue(colPos, rowPos, values[valueIdx++]); + } + } + return !HasErrorState(); + } //------------------------------------------------------------------- // Populate headings. - public bool SetColumnHeadings(List columns, SaMsExcelColumnHeading_Config config) + public bool SetColumnHeadings(List columns, SaMsExcelColumnHeading_Config headingConfig) { ClearErrorState(); - double columnWidthPixelsPerCm = Math.Min(100.0, Math.Max(10.0, config.ColumnWidthPixelsPerCm)); - uint firstHeadingRow = (uint)Math.Max(1, config.FirstHeadingRow); + double columnWidthPixelsPerCm = Math.Min(100.0, Math.Max(10.0, headingConfig.ColumnWidthPixelsPerCm)); + uint firstHeadingRowPos = (uint)Math.Max(1, headingConfig.FirstHeadingRowPos); + // Assume only a single header row. + headingConfig.FirstDataRowPos = firstHeadingRowPos + 1; try { // Populate default heading cell attributes. - SaMsExcelItemAttr attrHdrDefault = new SaMsExcelItemAttr(); - attrHdrDefault.Bold = config.HeadingTextBold; - attrHdrDefault.FontSize = (short)Math.Min(80, Math.Max(2, (int)config.HeadingFontSize)); - attrHdrDefault.InteriorColourIndex = config.InteriorColourIndex; - if (config.InteriorColour != null) - attrHdrDefault.InteriorColour = config.InteriorColour; - if (config.BorderWeight != SaXlBorderWeight.NotSpecified) + SaMsExcelStyleAttr attrHdrDefault = new SaMsExcelStyleAttr(); + attrHdrDefault.FontAttr.CopyFrom(headingConfig.FontAttr); + attrHdrDefault.WrapText = headingConfig.HeadingTextWrap ? true : null; + attrHdrDefault.VerticalAlignment = headingConfig.VerticalAlignment; + attrHdrDefault.ForegroundColourIndex = headingConfig.ForegroundColourIndex; + attrHdrDefault.BackgroundColourIndex = headingConfig.BackgroundColourIndex; + if (headingConfig.ForegroundColourRGB != null) + attrHdrDefault.ForegroundColourRGB = headingConfig.ForegroundColourRGB; + if (headingConfig.BorderWeight != SaXlBorderWeight.NotSpecified) { - attrHdrDefault.BorderLeft.Weight = config.BorderWeight; - attrHdrDefault.BorderTop.Weight = config.BorderWeight; - attrHdrDefault.BorderRight.Weight = config.BorderWeight; - attrHdrDefault.BorderBottom.Weight = config.BorderWeight; - attrHdrDefault.BorderInsideVertical.Weight = config.BorderWeight; + attrHdrDefault.BorderLeft.Weight = headingConfig.BorderWeight; + attrHdrDefault.BorderTop.Weight = headingConfig.BorderWeight; + attrHdrDefault.BorderRight.Weight = headingConfig.BorderWeight; + attrHdrDefault.BorderBottom.Weight = headingConfig.BorderWeight; } - SaMsExcelRange rangeHeadings = new SaMsExcelRange(config.FirstHeadingColumn, firstHeadingRow, (uint)(config.FirstHeadingColumn + columns.Count - 1), firstHeadingRow); + SaMsExcelRange rangeHeadings = new SaMsExcelRange(headingConfig.FirstHeadingColPos, firstHeadingRowPos, (uint)(headingConfig.FirstHeadingColPos + columns.Count - 1), firstHeadingRowPos); SetRangeAttr(rangeHeadings, attrHdrDefault); // Set default column attributes. bool defaultColumnAttributesPopulated = false; - SaMsExcelItemAttr attrColDefault = new SaMsExcelItemAttr(); - if (config.SetColumnAlignment) + SaMsExcelStyleAttr attrColDefault = new SaMsExcelStyleAttr(); + if (headingConfig.SetColumnAlignment) { attrColDefault.HorizontalAlignment = (short)XlHAlign.xlHAlignLeft; defaultColumnAttributesPopulated = true; } - if (config.SetColumnDataTypeFormat) + if (headingConfig.SetColumnDataTypeFormat) { - attrColDefault.NumberFormat = "@"; // '@' = 'Text' format. + attrColDefault.DataFormat = "@"; // '@' = 'Text' format. defaultColumnAttributesPopulated = true; } if (defaultColumnAttributesPopulated) { - SaMsExcelRange rangeColumns = new SaMsExcelRange(config.FirstHeadingColumn, 0, (uint)(config.FirstHeadingColumn + columns.Count - 1), 0); + SaMsExcelRange rangeColumns = new SaMsExcelRange(headingConfig.FirstHeadingColPos, 0, (uint)(headingConfig.FirstHeadingColPos + columns.Count - 1), 0); SetRangeAttr(rangeColumns, attrColDefault); } - // Populate column headings and specific styles. - List values = new List(); + // Populate column styling and headings. + List columnHeadings = new List(); for (int columnIdx = 0; columnIdx < columns.Count; columnIdx++) { ColumnDfn column = columns[columnIdx]; - uint col = (uint)(Math.Max(1, config.FirstHeadingColumn) + columnIdx); - SaMsExcelRange range = new SaMsExcelRange(col, firstHeadingRow); + uint colPos = (uint)(Math.Max(1, headingConfig.FirstHeadingColPos) + columnIdx); + SaMsExcelRange range = new SaMsExcelRange(colPos, firstHeadingRowPos); - // Specific heading cell attributes - none supported as yet. + // Specific heading cell attributes. bool headingCellHasSpecificAttr = false; - SaMsExcelItemAttr attrHdr = new SaMsExcelItemAttr(); + SaMsExcelStyleAttr attrHdr = new SaMsExcelStyleAttr(); if (headingCellHasSpecificAttr) SetRangeAttr(range, attrHdr); // Column width and caption: - if (config.SetColumnWidths) SetColWidth(col, column.Width / columnWidthPixelsPerCm, SaExcelLogicalUnit.Cm); - values.Add(column.Caption); + if (headingConfig.SetColumnWidths) SetColWidth(colPos, column.Width * 1.5 / columnWidthPixelsPerCm, SaExcelLogicalUnit.Cm); + columnHeadings.Add(column.Caption); // Column attributes: - bool columnHasSpecificAttributes = false; - SaMsExcelItemAttr attrCol = new SaMsExcelItemAttr(); - if (config.SetColumnAlignment && column.Alignment != ColumnAlignment.Left && column.Alignment != ColumnAlignment.Default) + bool columnHasSpecificStyle = false; + SaMsExcelStyleAttr attrCol = new SaMsExcelStyleAttr(); + if (headingConfig.SetColumnAlignment && column.Alignment != ColumnAlignment.Left && column.Alignment != ColumnAlignment.Default) { - columnHasSpecificAttributes = true; + columnHasSpecificStyle = true; switch (column.Alignment) { case ColumnAlignment.Center: attrCol.HorizontalAlignment = (short)XlHAlign.xlHAlignCenter; break; case ColumnAlignment.Right: attrCol.HorizontalAlignment = (short)XlHAlign.xlHAlignRight; break; } } - if (config.SetColumnDataTypeFormat && column.DataType != DataValue.DataType.String) + if (column.WrapText) { - columnHasSpecificAttributes = true; + columnHasSpecificStyle = true; + attrCol.WrapText = true; + } + if (headingConfig.SetColumnDataTypeFormat && column.DataType != DataValue.DataType.String) + { + columnHasSpecificStyle = true; if (column.DataType == DataValue.DataType.Long || column.DataType == DataValue.DataType.Double || column.DataType == DataValue.DataType.Decimal) { // Numeric format: "#,##0.00" @@ -388,63 +511,66 @@ namespace Sarand.Net.Standard.Proc.Excel } if ((column.DataType == DataValue.DataType.Double || column.DataType == DataValue.DataType.Decimal) && column.NumDecimals > 0) numberFormat += "." + "0".PadRight(column.NumDecimals, '0'); - attrCol.NumberFormat = numberFormat + ";[Red]-" + numberFormat; + attrCol.DataFormat = numberFormat + ";[Red]-" + numberFormat; } - else if (column.DataType == DataValue.DataType.Date) attrCol.NumberFormat = config.CellFormatDate; - else if (column.DataType == DataValue.DataType.Time) attrCol.NumberFormat = config.CellFormatTime; - else if (column.DataType == DataValue.DataType.DateTime) attrCol.NumberFormat = config.CellFormatDate + config.CellFormatDateTimeDelim + config.CellFormatTime; + else if (column.DataType == DataValue.DataType.Date) attrCol.DataFormat = headingConfig.CellFormatDate; + else if (column.DataType == DataValue.DataType.Time) attrCol.DataFormat = headingConfig.CellFormatTime; + else if (column.DataType == DataValue.DataType.DateTime) attrCol.DataFormat = headingConfig.CellFormatDate + headingConfig.CellFormatDateTimeDelim + headingConfig.CellFormatTime; } - if (columnHasSpecificAttributes) - SetRangeAttr(new SaMsExcelRange(col, 0), attrCol); + if (columnHasSpecificStyle) + SetRangeAttr(new SaMsExcelRange(colPos, 0), attrCol); // Freeze the window below if a colmn has been specified: - if (config.FreezeColumn > 0) FreezePanes(config.FreezeColumn, firstHeadingRow + 1); + if (headingConfig.FreezeColPos > 0) FreezePanes(headingConfig.FreezeColPos, firstHeadingRowPos + 1); } + // Populate header captions. - SetRowFormulas(config.FirstHeadingColumn, firstHeadingRow, values); - // Set header row to wrap text: - if (config.HeadingTextWrap) - { - SaMsExcelItemAttr attrRow = new SaMsExcelItemAttr(); - attrRow.WrapText = true; - SetRangeAttr(new SaMsExcelRange(0, firstHeadingRow), attrRow); - // Auto fit the header row (set height to fit text): - AutoFit(new SaMsExcelRange(0, firstHeadingRow)); - } + SetRowValues(headingConfig.FirstHeadingColPos, firstHeadingRowPos, columnHeadings); + + // Auto fit header row if text is wrapped: + if (headingConfig.HeadingTextWrap) + AutoFitRows(rangeHeadings); } - catch (Exception exc) { HandleException("SetColumnHeadings", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } //------------------------------------------------------------------- // Cell attributes. - public abstract bool SetRangeAttr(SaMsExcelRange range, SaMsExcelItemAttr attr); + public abstract bool SetRangeAttr(SaMsExcelRange range, SaMsExcelStyleAttr attr); - //------------------------------------------------------------------- // Add image to sheet. public abstract bool InsertImage(SaMsExcelImage_Config imageConfig); - //------------------------------------------------------------------- + /// Freeze sheet at specified cell. public abstract bool FreezePanes(uint colPos, uint rowPos); - //------------------------------------------------------------------- - public abstract bool AutoFit(SaMsExcelRange range); + /// Auto-fit row heights to contents. + public abstract bool AutoFitRows(SaMsExcelRange range); - //------------------------------------------------------------------- + /// Select range of cells. public abstract bool SelectRange(SaMsExcelRange range); //------------------------------------------------------------------- // Utilities. + + /// + /// Convert cell range to Id. + /// - Not fixed: "A9:AB34" + /// - Fixed cols: "$A9:$AB34" + /// - Fixed rows: "A$9:AB$34" + /// - Fixed: "$A$9:$AB$34" + /// public static String GetRangeId(SaMsExcelRange range, bool fixedCols = false, bool fixedRows = false) { - // Not fixed: "A9:AB34" - // Fixed cols: "$A9:$AB34" - // Fixed rows: "A$9:AB$34" - // Fixed: "$A$9:$AB$34" - return (fixedCols ? "$" : "") + GetColumnPrefix(range.ColFrom) + (fixedRows ? "$" : "") + GetRowSuffix(range.RowFrom) + ":" + (fixedCols ? "$" : "") + GetColumnPrefix(range.ColTo) + (fixedRows ? "$" : "") + GetRowSuffix(range.RowTo); + return GetColumnIdPrefix(range.ColFromPos, fixedCols) + GetRowIdSuffix(range.RowFromPos, fixedRows) + ":" + GetColumnIdPrefix(range.ColToPos, fixedCols) + GetRowIdSuffix(range.RowToPos, fixedRows); } - public static String GetColumnPrefix(uint colPos) + + /// + /// Convert column position to Id prefix. 1=A,2=B,3=C...26=Z,27=AA,28=AB etc. + /// - Fixed prefixes with '$'. + /// + public static String GetColumnIdPrefix(uint colPos, bool fixedCol = false) { - // 1=A,2=B,3=C...26=Z,27=AA,28=AB etc uint dividend = colPos; string columnName = String.Empty; uint modulo = 0; @@ -454,35 +580,51 @@ namespace Sarand.Net.Standard.Proc.Excel columnName = Convert.ToChar(65 + modulo).ToString() + columnName; dividend = (uint)((dividend - modulo) / 26); } - return columnName; + return (fixedCol ? "$" : "") + columnName; } - public static int GetIndexForCol(String column) + + /// Convert column Id to position. A=1,B=2,C=3...Z=26,AA=27 etc. + public static int GetPosForColId(String colId) { - // A=1,B=2,C=3...Z=26,AA=27 etc - int col = (column[0] - 'A'); - if (column.Length > 1) - col = (column[1] * 26) + column[0]; + colId = StringUtil.TrimWhitespace(StringUtil.OnBlank(colId)).ToUpper(); + if (!colId.Matches(new Regex("[A-Z]+"))) + { + SaLogger.Warn(SysUtil.Fn + " Invalid column Id supplied: '" + colId + "'"); + return -1; + } + int col = (colId[0] - 'A'); + if (colId.Length > 1) + col = ((colId[1] - 'A') * 26) + (colId[0] - 'A'); return col; } + + /// Column Id - To reference entire column: A:A,B:A,C:A...Z:Z,AA:AA etc. public static String GetColumnId(uint colPos) { - // To reference entire column: A:A,B:A,C:A...Z:Z,AA:AA etc - return GetColumnPrefix(colPos) + ":" + GetColumnPrefix(colPos); + return GetColumnIdPrefix(colPos) + ":" + GetColumnIdPrefix(colPos); } - public static String GetRowSuffix(uint rowPos) + + /// + /// Convert row pos to id suffix. 1=1,2=2,3=3...26=26,27=27,28=28 etc. + /// - Fixed prefixes with '$'. + /// + public static String GetRowIdSuffix(uint rowPos, bool fixedRow = false) { - // 1,2,3...26,27,28 etc - return (rowPos > 0 ? rowPos.ToString() : ""); + return (rowPos > 0 ? (fixedRow ? "$" : "") + rowPos.ToString() : ""); } + + /// Row id - To reference entire row: 1:1,2:2,3:3 etc. public static String GetRowId(uint rowPos) { - // To reference entire row: 1:1,2:2,3:3 etc - return GetRowSuffix(rowPos) + ":" + GetRowSuffix(rowPos); + return GetRowIdSuffix(rowPos) + ":" + GetRowIdSuffix(rowPos); } + + /// Convert cell by column and row positions to Id. public static String GetCellId(uint colPos, uint rowPos) { - return GetColumnPrefix(colPos) + GetRowSuffix(rowPos); + return GetColumnIdPrefix(colPos) + GetRowIdSuffix(rowPos); } + public static DateTime ExtractDate(String value) { DateTime date = DateTime.MinValue; @@ -502,7 +644,7 @@ namespace Sarand.Net.Standard.Proc.Excel //------------------------------------------------------------------- public static double PointsPerInch { get; } = 72; public static double PixelsPerInch { get; } = 96; - public static double CmsPerInch { get; } = 2.54f; + public static double CmsPerInch { get; } = 2.54; public double ConvertLength(double fLength, SaExcelLogicalUnit eUnitFrom, SaExcelLogicalUnit eUnitTo, bool bVertical) { // Convert length to points: @@ -559,7 +701,7 @@ namespace Sarand.Net.Standard.Proc.Excel //------------------------------------------------------------------- protected void HandleException(String fn, Exception exc) { - SaLogger.Error(GetType().Name + "." + fn + " " + ExceptionUtil.GetLogMessage(exc)); + SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc)); SetErrorState("Problems were encountered interfacing to Excel - " + ExceptionUtil.GetErrorStateMessage(exc)); } diff --git a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_Direct.cs b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_Direct.cs index 84b36a9..a9d8e20 100644 --- a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_Direct.cs +++ b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_Direct.cs @@ -18,7 +18,7 @@ namespace Sarand.Net.Standard.Proc.Excel /// Style properties retrieved directly from spreadsheet xml data. public class SaMsExcelStyle { - public SaMsExcelStyle () { } + public SaMsExcelStyle() { } public int NumFmtId { get; set; } public int FontId { get; set; } public int FillId { get; set; } @@ -51,7 +51,7 @@ namespace Sarand.Net.Standard.Proc.Excel internal SaMsXmlDocument StylesDoc { get; set; } internal uint WorksheetCount { get; set; } internal uint ActiveSheetPos { get; set; } - internal bool IsValid () { return (WorkbookDoc != null && WorkbookDoc.GetRootElement() != null); } + internal bool IsValid() { return (WorkbookDoc != null && WorkbookDoc.GetRootElement() != null); } } private Workbook workbook; private class Worksheet @@ -71,17 +71,17 @@ namespace Sarand.Net.Standard.Proc.Excel internal SaMsXmlElement ActiveRow { get; set; } } internal NodesData Nodes { get; } = new NodesData(); - bool IsValid () { return (WorksheetDoc != null && WorksheetDoc.GetRootElement() != null); } + bool IsValid() { return (WorksheetDoc != null && WorksheetDoc.GetRootElement() != null); } } private Worksheet worksheet; //------------------------------------------------------------------- // Construction. - public SaMsSpreadsheet_Direct () : base() + public SaMsSpreadsheet_Direct() : base() { } - protected override void Dispose (bool disposing) + protected override void Dispose(bool disposing) { if (disposing) { @@ -96,11 +96,11 @@ namespace Sarand.Net.Standard.Proc.Excel //------------------------------------------------------------------- // W o r k b o o k //------------------------------------------------------------------- - public override bool IsWorkbookValid () + public override bool IsWorkbookValid() { return (workbook != null && workbook.IsValid()); } - public override bool CreateWorkbook () + public override bool CreateWorkbook() { CloseWorkbook(); ClearErrorState(); @@ -113,7 +113,7 @@ namespace Sarand.Net.Standard.Proc.Excel return SetErrorState("Creating workbook not supported"); } - public override bool OpenWorkbook (String filePath) + public override bool OpenWorkbook(String filePath) { CloseWorkbook(); ClearErrorState(); @@ -138,7 +138,7 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public bool OpenWorkbook (byte[] workbookData) + public bool OpenWorkbook(byte[] workbookData) { CloseWorkbook(); ClearErrorState(); @@ -152,7 +152,7 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public bool OpenWorkbook (Stream workbookStream) + public bool OpenWorkbook(Stream workbookStream) { CloseWorkbook(); ClearErrorState(); @@ -172,7 +172,7 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - private bool ExtractWorkbookElementsFromArchive () + private bool ExtractWorkbookElementsFromArchive() { ClearErrorState(); try @@ -214,19 +214,22 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public override bool SaveDocument (String filePath, int fileFormat, bool bAutomaticallyOverwrite) + public override bool CleanseWorkbook() + { + throw new NotSupportedException(SysUtil.Fn); + } + public override bool SaveDocument(String filePath, int fileFormat, bool bAutomaticallyOverwrite) { ClearErrorState(); if (fileFormat == 0) fileFormat = (int)XlFileFormat.xlOpenXMLWorkbook; if (fileFormat != (int)XlFileFormat.xlOpenXMLWorkbook) - SetErrorState("Unsupported file format: " + ((XlFileFormat)fileFormat).ToString()); - if (!HasErrorState() && !workbook.IsValid()) - SetErrorState("Workbook is not open"); + return SetErrorState("Unsupported file format: " + ((XlFileFormat)fileFormat).ToString()); + if (!IsWorkbookValid()) SetErrorState(Msg_WorkbookNotOpen); SetErrorState("Saving workbook not supported"); return !HasErrorState(); } - public override bool CloseWorkbook () + public override bool CloseWorkbook() { ClearErrorState(); CloseWorksheet(); @@ -247,43 +250,60 @@ namespace Sarand.Net.Standard.Proc.Excel return !HasErrorState(); } - //------------------------------------------------------------------- - // W o r k s h e e t - //------------------------------------------------------------------- - public override bool IsWorksheetValid () + /// Extract the document as a byte array. + /// Document data as byte array. + public override byte[] GetDocumentData() { - return (worksheet != null && worksheet.WorksheetDoc != null); + throw new NotSupportedException(SysUtil.Fn); } + + // Default style. + public override void ApplyDefaultStyle(SaMsExcelStyleAttr attr) + { + throw new NotSupportedException(SysUtil.Fn); + } + + //------------------------------------------------------------------- + // W o r k s h e e t s + //------------------------------------------------------------------- + public override uint WorksheetCount { get { return workbook.WorksheetCount; } } - private void PopulateWorksheetCount () + private void PopulateWorksheetCount() { if (!IsWorkbookValid()) return; try { // Count worksheets in workbook. SaMsXmlElement nodeWorkbook = workbook.WorkbookDoc.GetRootElement(); - SaMsXmlElement nodeSheets = nodeWorkbook.GetFirstChildElement("sheets"); + SaMsXmlElement nodeSheets = nodeWorkbook.GetFirstChildElement("sheets"); workbook.WorksheetCount = (uint)nodeSheets.GetChildElements("sheet").Count; } - catch (Exception exc) { HandleException("GetWorksheetCount", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } } - public override bool SetWorksheetCount (int worksheetCount) + public override bool SetWorksheetCount(int worksheetCount) { ClearErrorState(); return SetErrorState("Modifying workbook not supported"); } - public override bool AddWorksheet(String name) + + //------------------------------------------------------------------- + public override List WorksheetNames { - return SetErrorState("Modifying workbook not supported"); + get + { + List names = new List(); + return names; + } } - public override bool RemoveWorksheet(uint worksheetPos) - { - return SetErrorState("Modifying workbook not supported"); - } - public override bool OpenWorksheet (uint worksheetPos) + + //------------------------------------------------------------------- + /// + /// Open worksheet by position (from 1). + /// + public override bool OpenWorksheet(uint worksheetPos) { ClearErrorState(); - if (!workbook.IsValid()) return SetErrorState("Workbook is not open"); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); if (worksheetPos > WorksheetCount) return SetErrorState("Invalid sheet specified"); if (worksheetPos <= 0) worksheetPos = Math.Max(1, workbook.ActiveSheetPos); CloseWorksheet(); @@ -292,7 +312,7 @@ namespace Sarand.Net.Standard.Proc.Excel worksheet = new Worksheet(); // Locate worksheet in workbook by 0-index. SaMsXmlElement workbookElement = workbook.WorkbookDoc.GetRootElement(); - SaMsXmlElement sheetsElement = workbookElement.GetFirstChildElement("sheets"); + SaMsXmlElement sheetsElement = workbookElement.GetFirstChildElement("sheets"); long sheetCount = 0; foreach (SaMsXmlElement sheetElement in sheetsElement.GetChildElements("sheet")) { @@ -329,20 +349,70 @@ namespace Sarand.Net.Standard.Proc.Excel } if (worksheet.WorksheetDoc == null) return SetErrorState("Unable to load worksheet " + worksheetPos.ToString()); } - catch (Exception exc) { HandleException("OpenWorksheet", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } - public override uint WorksheetPos { get { return workbook.ActiveSheetPos; } } - public override String WorksheetName + + //------------------------------------------------------------------- + /// + /// Open worksheet by position (from 1). + /// + public override bool OpenWorksheet(String name) { - get { return ""; } - set { } + ClearErrorState(); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); + CloseWorksheet(); + name = StringUtil.TrimWhitespace(name); + + try + { + worksheet = new Worksheet(); + // Locate worksheet in workbook by 0-index. + SaMsXmlElement workbookElement = workbook.WorkbookDoc.GetRootElement(); + SaMsXmlElement sheetsElement = workbookElement.GetFirstChildElement("sheets"); + uint worksheetPos = 0; + foreach (SaMsXmlElement sheetElement in sheetsElement.GetChildElements("sheet")) + { + worksheetPos++; + String sheetName = sheetElement.GetAttributeValue("name"); + if (StringUtil.TrimWhitespace(sheetName).EqualsCI(name)) + { + // Extract the worksheet data. + uint sheetId = (uint)StringUtil.ToInt(sheetElement.GetAttributeValue("sheetId")); + String worksheetArchivePath = "xl/worksheets/sheet" + worksheetPos.ToString() + ".xml"; + using (MemoryStream worksheetFile = workbook.WorkbookArchive.ExtractFileToStream(worksheetArchivePath)) + { + if (worksheetFile == null) return SetErrorState("Failed to load worksheet data"); + worksheet.WorksheetDoc = new SaMsXmlDocument(); + if (worksheet.WorksheetDoc.LoadXml(worksheetFile)) + { + workbook.ActiveSheetPos = worksheetPos; + worksheet.WorksheetPos = worksheetPos; + worksheet.WorksheetId = sheetId; + worksheet.WorksheetArchivePath = worksheetArchivePath; + worksheet.WorksheetName = sheetName; + worksheet.ActiveColPos = 1; + worksheet.ActiveRowPos = 1; + SaMsXmlElement nodeWorksheet = worksheet.WorksheetDoc.GetRootElement(); + worksheet.Nodes.Cols = nodeWorksheet.GetFirstChildElement("cols"); + worksheet.Nodes.ActiveCol = worksheet.Nodes.Cols.GetFirstChildElement("col"); + worksheet.Nodes.Rows = nodeWorksheet.GetFirstChildElement("sheetData"); + worksheet.Nodes.ActiveRow = worksheet.Nodes.Rows.GetFirstChildElement("row"); + workbook.ActiveSheetPos = worksheetPos; + } + else return SetErrorState("Failed to read data for worksheet " + worksheetPos.ToString()); + } + break; + } + } + if (worksheet.WorksheetDoc == null) return SetErrorState("Unable to load worksheet " + worksheetPos.ToString()); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } - public override bool SetWorksheetAttr (SaMsExcelItemAttr attr) - { - return SetErrorState("Not supported"); - } - public override bool CloseWorksheet () + + //------------------------------------------------------------------- + public override bool CloseWorksheet() { ClearErrorState(); @@ -364,24 +434,51 @@ namespace Sarand.Net.Standard.Proc.Excel } //------------------------------------------------------------------- - // Worksheet Page Setup. - public override bool SetPageSetup (SaMsExcelPageSetup_Config pageSetupConfig) + public override bool AddWorksheet(String name) { - return SetErrorState("Not supported"); + return SetErrorState("Modifying workbook not supported"); } - public override bool SetPageBreak (uint col, uint row) + public override bool RemoveWorksheet(uint worksheetPos) { - return SetErrorState("Not supported"); + return SetErrorState("Modifying workbook not supported"); + } + + //------------------------------------------------------------------- + // W o r k s h e e t + //------------------------------------------------------------------- + public override bool IsWorksheetValid() + { + return (worksheet != null && worksheet.WorksheetDoc != null); + } + + //------------------------------------------------------------------- + public override uint WorksheetPos { get { return workbook.ActiveSheetPos; } } + public override String WorksheetName + { + get { return ""; } + set { } + } + public override bool SetWorksheetAttr(SaMsExcelStyleAttr attr) + { + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); + } + + //------------------------------------------------------------------- + // Worksheet Page Setup. + public override bool SetPageSetup(SaMsExcelPageSetup_Config pageSetupConfig) + { + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); + } + public override bool SetPageBreak(uint col, uint row) + { + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- // C o l u m n s //------------------------------------------------------------------- - public override uint GetActiveColPos () - { - return worksheet.ActiveColPos; - } - private SaMsXmlElement GetColElement (uint colPos) + public override uint ActiveColPos { get { return worksheet.ActiveColPos; } } + private SaMsXmlElement GetColElement(uint colPos) { ClearErrorState(); try @@ -399,10 +496,10 @@ namespace Sarand.Net.Standard.Proc.Excel } } } - catch (Exception exc) { HandleException("GetColElement", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return new SaMsXmlElement(null); } - public override double GetColWidth (uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + public override double GetColWidth(uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { ClearErrorState(); try @@ -416,22 +513,35 @@ namespace Sarand.Net.Standard.Proc.Excel if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(width); return width; } - catch (Exception exc) { HandleException("GetColWidth", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return 0.0; } - public override bool SetColWidth (uint colPos, double fWidth, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + public override bool SetColWidth(uint colPos, double fWidth, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- // R o w s //------------------------------------------------------------------- - public override uint GetActiveRowPos () + public override uint FirstRowPos { - return worksheet.ActiveRowPos; + get + { + SaMsXmlElement rowElement = worksheet.Nodes.Rows.GetChildElements("row").First(); + return (uint)(rowElement?.GetAttributeValue("r").ToLong() ?? 0); + } } - SaMsXmlElement GetRowElement (uint rowPos) + public override uint LastRowPos + { + get + { + SaMsXmlElement rowElement = worksheet.Nodes.Rows.GetChildElements("row").Last(); + return (uint)(rowElement?.GetAttributeValue("r").ToLong() ?? 0); + } + } + public override uint ActiveRowPos { get { return worksheet.ActiveRowPos; } } + SaMsXmlElement GetRowElement(uint rowPos) { ClearErrorState(); try @@ -448,10 +558,10 @@ namespace Sarand.Net.Standard.Proc.Excel } } } - catch (Exception exc) { HandleException("GetRowElement", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return new SaMsXmlElement(null); } - public override double GetRowHeight (uint rowPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + public override double GetRowHeight(uint rowPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { ClearErrorState(); try @@ -465,18 +575,18 @@ namespace Sarand.Net.Standard.Proc.Excel if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(height); return height; } - catch (Exception exc) { HandleException("GetRowHeight", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return 0.0; } - public override bool SetRowHeight (uint rowPos, double fHeight, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + public override bool SetRowHeight(uint rowPos, double fHeight, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- // R o w s //------------------------------------------------------------------- - public override List GetRowDataValues (uint rowPos, int maxColumns = 0) + public override List GetRowValues(uint rowPos, int maxColumns = 0) { ClearErrorState(); List values = new List(); @@ -492,17 +602,17 @@ namespace Sarand.Net.Standard.Proc.Excel if (maxColumns > 0 && values.Count >= maxColumns) break; } } - catch (Exception exc) { HandleException("GetRowDataValues", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return values; } //------------------------------------------------------------------- // C e l l s //------------------------------------------------------------------- - SaMsXmlElement GetCellElement (uint colPos, uint rowPos) + SaMsXmlElement GetCellElement(uint colPos, uint rowPos) { ClearErrorState(); - String cellId = GetCellId(colPos,rowPos); + String cellId = GetCellId(colPos, rowPos); try { SaMsXmlElement rowElement = GetRowElement(rowPos); @@ -512,88 +622,99 @@ namespace Sarand.Net.Standard.Proc.Excel return cellElement; } } - catch (Exception exc) { HandleException("GetCellElement", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return new SaMsXmlElement(null); } - public override void SetActiveCell (uint colPos, uint rowPos) + public override void SetActiveCell(uint colPos, uint rowPos) { // ToDo SetActiveCell. - SetErrorState("Not supported"); + SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - private bool IsCellFormula (uint colPos, uint rowPos) + private bool IsCellFormula(uint colPos, uint rowPos) { ClearErrorState(); bool bIsFormula = false; try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); + SaMsXmlElement cellElement = GetCellElement(colPos, rowPos); bIsFormula = !cellElement.GetAttributeValue("f").IsEmpty(); } - catch (Exception exc) { HandleException("IsCellFormula", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return bIsFormula; } - private bool IsCellNumeric (uint colPos, uint rowPos) + private bool IsCellNumeric(uint colPos, uint rowPos) { ClearErrorState(); try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); + SaMsXmlElement cellElement = GetCellElement(colPos, rowPos); + SaMsExcelStyle cellStyle = GetCellStyle(cellElement); return IsCellNumeric(cellElement, cellStyle); } - catch (Exception exc) { HandleException("IsCellNumeric", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return false; } - private bool IsCellNumeric (SaMsXmlElement cellElement) + private bool IsCellNumeric(SaMsXmlElement cellElement) { ClearErrorState(); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); + SaMsExcelStyle cellStyle = GetCellStyle(cellElement); return IsCellNumeric(cellElement, cellStyle); } - private bool IsCellNumeric (SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) + private bool IsCellNumeric(SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) { ClearErrorState(); return (cellElement.GetAttributeValue("t").IsEmpty() && !IsDateTimeStyle(cellStyle.NumFmtCode)); } - private bool IsCellDate (uint colPos, uint rowPos) + private bool IsCellDate(uint colPos, uint rowPos) { ClearErrorState(); bool bIsDate = false; try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); + SaMsXmlElement cellElement = GetCellElement(colPos, rowPos); + SaMsExcelStyle cellStyle = GetCellStyle(cellElement); return IsCellDate(cellElement, cellStyle); } - catch (Exception exc) { HandleException("IsCellDate", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return bIsDate; } - private bool IsCellDate (SaMsXmlElement cellElement) + private bool IsCellDate(SaMsXmlElement cellElement) { return IsCellDate(cellElement, GetCellStyle(cellElement)); } - private bool IsCellDate (SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) + private bool IsCellDate(SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) { ClearErrorState(); return IsDateTimeStyle(cellStyle.NumFmtCode); } //------------------------------------------------------------------- - public override String GetCellFormula (uint colPos, uint rowPos) + public override String GetCellFormula(uint colPos, uint rowPos) { ClearErrorState(); try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); + SaMsXmlElement cellElement = GetCellElement(colPos, rowPos); String formula = cellElement.GetAttributeValue("f"); if (formula.IsEmpty()) formula = GetCellText(cellElement, true); return formula; } - catch (Exception exc) { HandleException("GetCellFormula", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return ""; } - private String GetCellText (SaMsXmlElement cellElement, bool trimValue = true) + public override String GetCellText(uint colPos, uint rowPos, bool trimWhitespace = true) + { + ClearErrorState(); + try + { + SaMsXmlElement cellElement = GetCellElement(colPos, rowPos); + return GetCellText(cellElement, true); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return ""; + } + private String GetCellText(SaMsXmlElement cellElement, bool trimValue = true) { ClearErrorState(); String value = ""; @@ -605,14 +726,14 @@ namespace Sarand.Net.Standard.Proc.Excel value = cellElement.GetChildText("v"); if (trimValue) value.TrimAll(); } - catch (Exception exc) { HandleException("GetCellText", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return value; } - public override DataValue GetCellDataValue (uint colPos, uint rowPos) + public override DataValue GetCellValue(uint colPos, uint rowPos) { return GetCellDataValue(GetCellElement(colPos, rowPos), false); } - private DataValue GetCellDataValue (SaMsXmlElement cellElement, bool trimTextValue = true) + private DataValue GetCellDataValue(SaMsXmlElement cellElement, bool trimTextValue = true) { ClearErrorState(); try @@ -645,12 +766,12 @@ namespace Sarand.Net.Standard.Proc.Excel return new DataValue(value); } } - catch (Exception exc) { HandleException("GetCellDataValue", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return new DataValue(); } //------------------------------------------------------------------- // Shared string for cell. - private String GetSharedString (SaMsXmlElement nodeElement) + private String GetSharedString(SaMsXmlElement nodeElement) { ClearErrorState(); try @@ -658,10 +779,10 @@ namespace Sarand.Net.Standard.Proc.Excel SaMsXmlElement sharedStringElement = GetSharedStringElement(nodeElement); return sharedStringElement.GetChildText("t"); } - catch (Exception exc) { HandleException("GetSharedString", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return ""; } - private SaMsXmlElement GetSharedStringElement (SaMsXmlElement cellElement) + private SaMsXmlElement GetSharedStringElement(SaMsXmlElement cellElement) { ClearErrorState(); if (workbook.SharedStringsDoc == null || cellElement == null || !cellElement.Valid) return null; @@ -678,31 +799,23 @@ namespace Sarand.Net.Standard.Proc.Excel return siElement; } } - catch (Exception exc) { HandleException("GetSharedStringNode", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return new SaMsXmlElement(null); } //------------------------------------------------------------------- - public override bool SetCellFormula (uint colPos, uint rowPos, String value) + public override bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelStyleAttr attr) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } - public override bool SetCellFormula (uint colPos, uint rowPos, String value, SaMsExcelItemAttr attr) + public override bool SetCellValue(uint colPos, uint rowPos, DataValue value, SaMsExcelStyleAttr attr) { - return SetErrorState("Not supported"); - } - public override bool SetRowFormulas (uint colPosStart, uint rowPos, List values) - { - return SetErrorState("Not supported"); - } - public override bool SetRangeFormulas (SaMsExcelRange range, List values) - { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- // C e l l S t y l e //------------------------------------------------------------------- - private String GetBuiltInStyleFormat (int numFmtId) + private String GetBuiltInStyleFormat(int numFmtId) { // Ref. http://stackoverflow.com/questions/4730152/what-indicates-an-office-open-xml-cell-contains-a-date-time-value switch (numFmtId) @@ -758,7 +871,7 @@ namespace Sarand.Net.Standard.Proc.Excel } return ""; } - private SaMsExcelStyle GetCellStyle (SaMsXmlElement cellElement) + private SaMsExcelStyle GetCellStyle(SaMsXmlElement cellElement) { ClearErrorState(); SaMsExcelStyle style = new SaMsExcelStyle(); @@ -769,7 +882,7 @@ namespace Sarand.Net.Standard.Proc.Excel { long cellStyleIdx = cellElement.GetAttributeValue("s").ToLong(); SaMsXmlElement styleSheetElement = workbook.SharedStringsDoc.GetRootElement(); - SaMsXmlElement cellXfsElement = styleSheetElement.GetFirstChildElement("cellXfs"); + SaMsXmlElement cellXfsElement = styleSheetElement.GetFirstChildElement("cellXfs"); foreach (SaMsXmlElement xfElement in styleSheetElement.GetChildElements("xf")) { if (++styleIdx == cellStyleIdx) @@ -845,10 +958,10 @@ namespace Sarand.Net.Standard.Proc.Excel } } } - catch (Exception exc) { HandleException("GetStyleNode", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return style; } - private bool IsDateStyle (String numFormat) + private bool IsDateStyle(String numFormat) { // Ref. http://stackoverflow.com/questions/19034805/how-to-distinguish-inline-numbers-from-ole-automation-date-numbers-in-openxml-sp/19582685#19582685 if (IsTimeStyle(numFormat)) return false; @@ -857,7 +970,7 @@ namespace Sarand.Net.Standard.Proc.Excel if (numFormat.Contains("y")) return true; return false; } - private bool IsTimeStyle (String numFormat) + private bool IsTimeStyle(String numFormat) { if (numFormat.Contains("h")) return true; if (numFormat.Contains(":mm")) return true; @@ -865,41 +978,41 @@ namespace Sarand.Net.Standard.Proc.Excel if (numFormat.Contains(":ss")) return true; return false; } - private bool IsDateTimeStyle (String numFormat) + private bool IsDateTimeStyle(String numFormat) { return (IsDateStyle(numFormat) || IsTimeStyle(numFormat)); } //------------------------------------------------------------------- // Cell attributes. - public override bool SetRangeAttr (SaMsExcelRange range, SaMsExcelItemAttr attr) + public override bool SetRangeAttr(SaMsExcelRange range, SaMsExcelStyleAttr attr) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- // Add image to sheet. - public override bool InsertImage (SaMsExcelImage_Config imageConfig) + public override bool InsertImage(SaMsExcelImage_Config imageConfig) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - public override bool FreezePanes (uint colPos, uint rowPos) + public override bool FreezePanes(uint colPos, uint rowPos) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - public override bool AutoFit (SaMsExcelRange range) + public override bool AutoFitRows(SaMsExcelRange range) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - public override bool SelectRange (SaMsExcelRange range) + public override bool SelectRange(SaMsExcelRange range) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- diff --git a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_OpenXml.cs b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_OpenXml.cs index 1eafb26..8ca36e0 100644 --- a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_OpenXml.cs +++ b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_OpenXml.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Utils; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace Sarand.Net.Standard.Proc.Excel { @@ -118,6 +116,10 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SaLogger.Warn(exc.GetType().Name + ".OpenWorkbook " + ExceptionUtil.GetErrorStateMessage(exc)); } return (workbook != null); } + public override bool CleanseWorkbook() + { + throw new NotSupportedException(SysUtil.Fn); + } public override bool SaveDocument(String filePath, int fileFormat, bool bAutomaticallyOverwrite) { ClearErrorState(); @@ -156,13 +158,22 @@ namespace Sarand.Net.Standard.Proc.Excel return !HasErrorState(); } - //------------------------------------------------------------------- - // W o r k s h e e t - //------------------------------------------------------------------- - public override bool IsWorksheetValid() + /// Extract the document as a byte array. + /// Document data as byte array. + public override byte[] GetDocumentData() { - return (worksheet != null); + throw new NotSupportedException(SysUtil.Fn); } + + // Default style. + public override void ApplyDefaultStyle(SaMsExcelStyleAttr attr) + { + throw new NotSupportedException(SysUtil.Fn); + } + + //------------------------------------------------------------------- + // W o r k s h e e t s + //------------------------------------------------------------------- public override uint WorksheetCount { get @@ -187,6 +198,11 @@ namespace Sarand.Net.Standard.Proc.Excel else SetErrorState("SetWorksheetCount - Requested number of worksheets (" + worksheetCount.ToString() + ") must be between 1 and 10"); return !HasErrorState(); } + + //------------------------------------------------------------------- + public override List WorksheetNames { get { return new List(); } } + + //------------------------------------------------------------------- public override bool AddWorksheet(String sheetName) { ClearErrorState(); @@ -214,6 +230,8 @@ namespace Sarand.Net.Standard.Proc.Excel return (worksheet != null); } + + //------------------------------------------------------------------- public override bool RemoveWorksheet(uint worksheetPos) { ClearErrorState(); @@ -243,9 +261,12 @@ namespace Sarand.Net.Standard.Proc.Excel } return false; } + + //------------------------------------------------------------------- + // W o r k s h e e t + //------------------------------------------------------------------- public override bool OpenWorksheet(uint worksheetPos) { - String fn = GetType().Name + ".OpenWorksheet"; ClearErrorState(); CloseWorksheet(); @@ -267,31 +288,49 @@ namespace Sarand.Net.Standard.Proc.Excel if (worksheetPart != null && sheet != null) worksheet = new WorksheetData(worksheetPos, worksheetPart, sheet); } - if (worksheet != null) { - SaLogger.Warn(fn + " Failed to open worksheet " + worksheetPos); + SaLogger.Warn(SysUtil.Fn + " Failed to open worksheet " + worksheetPos); SetErrorState("Failed to open worksheet " + worksheetPos); } - return (worksheet != null); } - public override uint WorksheetPos { get { return activeWorksheetPos; } } - public override String WorksheetName - { - get { return worksheet.Sheet.Name; } - set { worksheet.Sheet.Name = StringUtil.Left(value, 31); } - } - public override bool SetWorksheetAttr(SaMsExcelItemAttr attr) + + public override bool OpenWorksheet(String name) { ClearErrorState(); - try + CloseWorksheet(); + name = StringUtil.TrimWhitespace(name); + + // Extract Sheet by position. + uint worksheetPos = 0; + Sheet sheet = null; + foreach (Sheet sheetCandidate in Sheets) { - // ToDo OpenXml SetWorksheetAttr + ++worksheetPos; + if (StringUtil.TrimWhitespace(sheetCandidate.Name).EqualsCI(name)) + { + sheet = sheetCandidate; + break; + } } - catch (Exception exc) { HandleException("SetWorksheetAttr", exc); } - return !HasErrorState(); + if (sheet != null) + { + WorksheetPart worksheetPart = (WorksheetPart)(WorkbookPart.GetPartById(sheet.Id)); + + if (worksheetPart != null && sheet != null) + worksheet = new WorksheetData(worksheetPos, worksheetPart, sheet); + } + if (worksheet != null) + { + String msg = "Failed to open worksheet: " + name; + SetErrorState(msg); + SaLogger.Warn(SysUtil.Fn + " " + msg); + } + return (worksheet != null); } + + //------------------------------------------------------------------- public override bool CloseWorksheet() { ClearErrorState(); @@ -303,12 +342,38 @@ namespace Sarand.Net.Standard.Proc.Excel } worksheet = null; } - catch (Exception exc) { HandleException("CloseWorksheet", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } worksheet = null; activeWorksheetPos = 0; return !HasErrorState(); } + //------------------------------------------------------------------- + public override bool IsWorksheetValid() + { + return (worksheet != null); + } + + //------------------------------------------------------------------- + public override uint WorksheetPos { get { return activeWorksheetPos; } } + public override String WorksheetName + { + get { return worksheet.Sheet.Name; } + set { worksheet.Sheet.Name = StringUtil.Left(value, 31); } + } + + //------------------------------------------------------------------- + public override bool SetWorksheetAttr(SaMsExcelStyleAttr attr) + { + ClearErrorState(); + try + { + // ToDo OpenXml SetWorksheetAttr + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); + } + //------------------------------------------------------------------- // Worksheet Page Setup. public override bool SetPageSetup(SaMsExcelPageSetup_Config pageSetupConfig) @@ -428,7 +493,7 @@ namespace Sarand.Net.Standard.Proc.Excel } */ } - catch (Exception exc) { HandleException("SetPageSetup", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } public override bool SetPageBreak(uint colPos, uint rowPos) @@ -445,7 +510,7 @@ namespace Sarand.Net.Standard.Proc.Excel worksheet.VPageBreaks.Add(range); */ } - catch (Exception exc) { HandleException("SetPageSetup", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } @@ -459,7 +524,7 @@ namespace Sarand.Net.Standard.Proc.Excel //------------------------------------------------------------------- // C o l u m n s //------------------------------------------------------------------- - private Column GetColumnByPos (uint columnPos) + private Column GetColumnByPos(uint columnPos) { if (worksheet.ActiveColumnPos != columnPos) { @@ -468,15 +533,18 @@ namespace Sarand.Net.Standard.Proc.Excel } return worksheet.ActiveColumn; } - public override uint GetActiveColPos() + public override uint ActiveColPos { - if (worksheet == null) return 0; - try + get { - return worksheet.ActiveColumnPos; + if (worksheet == null) return 0; + try + { + return worksheet.ActiveColumnPos; + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return 0; } - catch (Exception exc) { HandleException("GetActiveCol", exc); } - return 0; } public override double GetColWidth(uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { @@ -485,7 +553,7 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML GetColWidth. } - catch (Exception exc) { HandleException("GetColWidth", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return columnWidth; } public override bool SetColWidth(uint colPos, double columnWidth, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) @@ -495,23 +563,30 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML SetColWidth. } - catch (Exception exc) { HandleException("SetColWidth", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } //------------------------------------------------------------------- // R o w s //------------------------------------------------------------------- - public override uint GetActiveRowPos() + public override uint FirstRowPos { get { return (uint)(0); } } + public override uint LastRowPos { get { return (uint)(0); } } + + //------------------------------------------------------------------- + public override uint ActiveRowPos { - try + get { - // ToDo OpenXML GetActiveRowPos. + try + { + // ToDo OpenXML GetActiveRowPos. + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return 0; } - catch (Exception exc) { HandleException("GetActiveRowPos", exc); } - return 0; } - public override List GetRowDataValues(uint rowPos, int maxColumns = 0) + public override List GetRowValues(uint rowPos, int maxColumns = 0) { List values = new List(); return values; @@ -523,7 +598,7 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML GetRowHeight. } - catch (Exception exc) { HandleException("GetRowHeight", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return rowHeight; } public override bool SetRowHeight(uint rowPos, double rowHeight, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) @@ -533,7 +608,7 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML SetRowHeight. } - catch (Exception exc) { HandleException("SetRowHeight", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } @@ -549,52 +624,50 @@ namespace Sarand.Net.Standard.Proc.Excel // ToDo OpenXML GetCellFormula. return ""; } - public override DataValue GetCellDataValue(uint colPos, uint rowPos) + public override String GetCellText(uint colPos, uint rowPos, bool trimWhitespace = true) + { + // ToDo OpenXML GetCellText. + return ""; + } + public override DataValue GetCellValue(uint colPos, uint rowPos) { // ToDo OpenXML GetCellDataValue. return new DataValue(); } - public override bool SetCellFormula(uint colPos, uint rowPos, String value) + + //------------------------------------------------------------------- + public override bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelStyleAttr attr) { // ToDo OpenXML SetCellFormula. return true; } - public override bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelItemAttr attr) + + public override bool SetCellValue(uint colPos, uint rowPos, DataValue value, SaMsExcelStyleAttr attr) { - // ToDo OpenXML SetCellFormula. - return true; - } - public override bool SetRowFormulas(uint colPosStart, uint rowPos, List values) - { - // ToDo OpenXML SetRowFormulas. - return true; - } - public override bool SetRangeFormulas(SaMsExcelRange range, List values) - { - // ToDo OpenXML SetRangeFormulas. + // ToDo OpenXML SetCellValue. return true; } //------------------------------------------------------------------- // Cell attributes. - public override bool SetRangeAttr(SaMsExcelRange range, SaMsExcelItemAttr attr) + public override bool SetRangeAttr(SaMsExcelRange range, SaMsExcelStyleAttr attr) { ClearErrorState(); try { // ToDo OpenXML SetRangeAttr. } - catch (Exception exc) { HandleException("SetRangeAttr", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } - private bool SetRangeAttr(Range oRange, SaMsExcelItemAttr attr) + private bool SetRangeAttr(Range oRange, SaMsExcelStyleAttr attr) { ClearErrorState(); try { // ToDo OpenXML SetRangeAttr. } - catch (Exception exc) { HandleException("SetRangeAttr", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } @@ -610,7 +683,7 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML InsertImage. } - catch (Exception exc) { HandleException("InsertImage", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } @@ -622,19 +695,19 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML FreezePanes. } - catch (Exception exc) { HandleException("FreezePanes", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } //------------------------------------------------------------------- - public override bool AutoFit(SaMsExcelRange range) + public override bool AutoFitRows(SaMsExcelRange range) { ClearErrorState(); try { // ToDo OpenXML AutoFit. } - catch (Exception exc) { HandleException("AutoFit", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } @@ -646,7 +719,7 @@ namespace Sarand.Net.Standard.Proc.Excel { // ToDo OpenXML SelectRange. } - catch (Exception exc) { HandleException("SelectRange", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } diff --git a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs index 242efd8..0fa0a68 100644 --- a/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs +++ b/SacsStandardProc/Proc/Excel/SaMsSpreadsheet_POI.cs @@ -1,65 +1,50 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Utils; using Sarand.Net.Standard.Proc.Xml; using Sarand.Net.Standard.Proc.Zip; +using NPOI.SS.UserModel; +using NPOI.XSSF.UserModel; +using NPOI.HSSF.UserModel; +using System.Linq; +using NPOI.SS.Formula.Functions; +using NPOI.SS.Util; +using System.Drawing; +using System.Security.Policy; +using MimeKit; +using DocumentFormat.OpenXml.Drawing.Charts; +using System.Reflection; +using Sarand.Net.Standard.Base.App; +using NPOI.HSSF.Util; +using NPOI.XSSF.Model; +using System.Drawing.Imaging; +using System.Xml.Linq; +using EnumsNET; +using NPOI.OOXML.XSSF.UserModel; namespace Sarand.Net.Standard.Proc.Excel { //======================================================================= - /// Excel spreadsheet read directly from xml data rather than via COM/Interop. + /// Excel spreadsheet as supported by NPOI. public class SaMsSpreadsheet_POI : SaMsSpreadsheet { //------------------------------------------------------------------- - // Data members. - internal class Workbook - { - internal SaMsZipArchive WorkbookArchive { get; set; } - internal SaMsXmlDocument WorkbookDoc { get; set; } - internal SaMsXmlDocument SharedStringsDoc { get; set; } - internal SaMsXmlDocument StylesDoc { get; set; } - internal uint WorksheetCount { get; set; } - internal uint ActiveSheetPos { get; set; } - internal bool IsValid () { return (WorkbookDoc != null && WorkbookDoc.GetRootElement() != null); } - } - private Workbook workbook; - private class Worksheet - { - internal uint WorksheetPos { get; set; } - internal uint WorksheetId { get; set; } - internal String WorksheetArchivePath { get; set; } - internal String WorksheetName { get; set; } - internal SaMsXmlDocument WorksheetDoc { get; set; } - internal uint ActiveColPos { get; set; } - internal uint ActiveRowPos { get; set; } - internal class NodesData - { - internal SaMsXmlElement Cols { get; set; } - internal SaMsXmlElement ActiveCol { get; set; } - internal SaMsXmlElement Rows { get; set; } - internal SaMsXmlElement ActiveRow { get; set; } - } - internal NodesData Nodes { get; } = new NodesData(); - bool IsValid () { return (WorksheetDoc != null && WorksheetDoc.GetRootElement() != null); } + // Definitions. - } - private Worksheet worksheet; + //------------------------------------------------------------------- + // Data members. + private IWorkbook workbook; + private ISheet worksheet; //------------------------------------------------------------------- // Construction. - public SaMsSpreadsheet_POI () : base() + public SaMsSpreadsheet_POI() : base() { } - protected override void Dispose (bool disposing) + protected override void Dispose(bool disposing) { if (disposing) { @@ -74,49 +59,45 @@ namespace Sarand.Net.Standard.Proc.Excel //------------------------------------------------------------------- // W o r k b o o k //------------------------------------------------------------------- - public override bool IsWorkbookValid () + public override bool IsWorkbookValid() { - return (workbook != null && workbook.IsValid()); + return workbook != null; } - public override bool CreateWorkbook () + public override bool CreateWorkbook() { CloseWorkbook(); ClearErrorState(); // Create new workbook. - workbook = new Workbook(); - workbook.WorkbookArchive = new SaMsZipArchive(); - workbook.SharedStringsDoc = new SaMsXmlDocument(); - workbook.StylesDoc = new SaMsXmlDocument(); - - return SetErrorState("Creating workbook not supported"); + workbook = new XSSFWorkbook(); + AddWorksheet("Sheet1"); + return true; } - public override bool OpenWorkbook (String filePath) + public override bool OpenWorkbook(String filePath) { CloseWorkbook(); ClearErrorState(); - if (!File.Exists(filePath)) - return SetErrorState("Workbook not found"); - List validExtensions = new List() { /*".xls",*/ ".xlsx", ".xlsb", ".zip" }; - String extension = Path.GetExtension(filePath); - if (!validExtensions.ContainsCI(Path.GetExtension(filePath))) - return SetErrorState("Invalid document type (expected xlsx/xlsb)"); - try { // Open workbook document. - workbook = new Workbook(); - workbook.WorkbookArchive = new SaMsZipArchive(); - if (workbook.WorkbookArchive.OpenArchive(filePath)) + using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { - ExtractWorkbookElementsFromArchive(); + try + { + // Open XML formats: xlsx etc. + workbook = new XSSFWorkbook(fs); + } + catch + { + // If reading fails, try to read workbook as XLS: + workbook = new HSSFWorkbook(fs); + } } - else return SetErrorState("Failed to open document: " + workbook.WorkbookArchive.ErrorStateMessage); } catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public bool OpenWorkbook (byte[] workbookData) + public bool OpenWorkbook(byte[] workbookData) { CloseWorkbook(); ClearErrorState(); @@ -130,7 +111,7 @@ namespace Sarand.Net.Standard.Proc.Excel catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public bool OpenWorkbook (Stream workbookStream) + public bool OpenWorkbook(Stream workbookStream) { CloseWorkbook(); ClearErrorState(); @@ -139,548 +120,882 @@ namespace Sarand.Net.Standard.Proc.Excel // From start of stream. workbookStream.Seek(0, SeekOrigin.Begin); // Open workbook document. - workbook = new Workbook(); - workbook.WorkbookArchive = new SaMsZipArchive(); - if (workbook.WorkbookArchive.OpenArchive(workbookStream)) + try { - ExtractWorkbookElementsFromArchive(); + workbook = new XSSFWorkbook(workbookStream); + } + catch + { + // If reading fails, try to read workbook as XLS: + workbook = new HSSFWorkbook(workbookStream); } - else return SetErrorState("Failed to open document: " + workbook.WorkbookArchive.ErrorStateMessage); } catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - private bool ExtractWorkbookElementsFromArchive () + + //------------------------------------------------------------------- + /// Cleanse workbook - remove unreferenced styles, fonts etc etc. + public override bool CleanseWorkbook() { ClearErrorState(); try { - // Extract the workbook data. - using (MemoryStream workbookFile = workbook.WorkbookArchive.ExtractFileToStream("xl/workbook.xml")) + if (workbook is HSSFWorkbook) { - if (workbookFile == null) return SetErrorState("Failed to load workbook data"); - workbook.WorkbookDoc = new SaMsXmlDocument(); - if (workbook.WorkbookDoc.LoadXml(workbookFile)) - { - // Workbook successfully opened. - workbook.ActiveSheetPos = 1; - // Shared strings: - using (MemoryStream sharedStringsFile = workbook.WorkbookArchive.ExtractFileToStream("xl/sharedStrings.xml")) - { - if (sharedStringsFile != null) - { - workbook.SharedStringsDoc = new SaMsXmlDocument(); - workbook.SharedStringsDoc.LoadXml(sharedStringsFile); - } - } - // Styles: - using (MemoryStream stylesFile = workbook.WorkbookArchive.ExtractFileToStream("xl/styles.xml")) - { - if (stylesFile != null) - { - workbook.StylesDoc = new SaMsXmlDocument(); - workbook.StylesDoc.LoadXml(stylesFile); - } - } - // Set worksheet count and open first worksheet. - PopulateWorksheetCount(); - OpenWorksheet(1); - } - else return SetErrorState("Failed to read workbook data"); + HSSFOptimiser.OptimiseCellStyles((HSSFWorkbook)workbook); + HSSFOptimiser.OptimiseFonts((HSSFWorkbook)workbook); + } + if (workbook is XSSFWorkbook) + { + // No equivalent XFFS functionality. } } - catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } + catch (Exception exc) { SetErrorState(SysUtil.Fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); } return !HasErrorState(); } - public override bool SaveDocument (String filePath, int fileFormat, bool bAutomaticallyOverwrite) + + //------------------------------------------------------------------- + /// Save spreadsheet to file. + /// + /// Ignored - depends on POI document version. + /// + /// + public override bool SaveDocument(String filePath, int fileFormat, bool bAutomaticallyOverwrite) { ClearErrorState(); if (fileFormat == 0) fileFormat = (int)XlFileFormat.xlOpenXMLWorkbook; if (fileFormat != (int)XlFileFormat.xlOpenXMLWorkbook) SetErrorState("Unsupported file format: " + ((XlFileFormat)fileFormat).ToString()); - if (!HasErrorState() && !workbook.IsValid()) - SetErrorState("Workbook is not open"); - SetErrorState("Saving workbook not supported"); - return !HasErrorState(); - } - public override bool CloseWorkbook () - { - ClearErrorState(); - CloseWorksheet(); - - // Clear workbook. - if (workbook != null) + if (!HasErrorState() && !IsWorkbookValid()) + SetErrorState(Msg_WorkbookNotOpen); + if (!HasErrorState()) { try { - if (workbook.StylesDoc != null) workbook.StylesDoc.Dispose(); workbook.StylesDoc = null; - if (workbook.SharedStringsDoc != null) workbook.SharedStringsDoc.Dispose(); workbook.SharedStringsDoc = null; - if (workbook.WorkbookDoc != null) workbook.WorkbookDoc.Dispose(); workbook.WorkbookDoc = null; - if (workbook.WorkbookArchive != null) workbook.WorkbookArchive.Dispose(); workbook.WorkbookArchive = null; + using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write)) + { + workbook.Write(fs); + } } catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } - workbook = null; } return !HasErrorState(); } + //------------------------------------------------------------------- + public override bool CloseWorkbook() + { + ClearErrorState(); + CloseWorksheet(); + // Clear workbook. + if (workbook != null) + workbook = null; + return !HasErrorState(); + } + + //------------------------------------------------------------------- + /// Extract the document as a byte array. + /// Document data as byte array. + public override byte[] GetDocumentData() + { + ClearErrorState(); + if (!HasErrorState() && !IsWorkbookValid()) + SetErrorState(Msg_WorkbookNotOpen); + + if (!HasErrorState()) + { + try + { + using (MemoryStream memStream = new MemoryStream()) + { + workbook.Write(memStream); + return memStream.ToArray(); + } + } + catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } + } + return null; + } + + //------------------------------------------------------------------- + // Default style. + public override void ApplyDefaultStyle(SaMsExcelStyleAttr attr) + { + try + { + // Zero font. + IFont font = workbook.GetFontAt(0); + ApplyFontAttr(font, attr); + // Zero style - is this the default for all cells?. + ICellStyle style = workbook.GetCellStyleAt(0); + ApplyAttrToCellStyle(attr, style); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + } + + //------------------------------------------------------------------- + // W o r k s h e e t s + //------------------------------------------------------------------- + // Worksheet count. + public override uint WorksheetCount { get { return (uint)workbook.NumberOfSheets; } } + public override bool SetWorksheetCount(int worksheetCount) + { + ClearErrorState(); + ClearErrorState(); + if (worksheetCount > 0 && worksheetCount < 11) + { + while (WorksheetCount < worksheetCount) + AddWorksheet(""); + while (WorksheetCount > worksheetCount) + RemoveWorksheet(WorksheetCount); + OpenWorksheet(1); + } + else SetErrorState(SysUtil.Fn + " - Requested number of worksheets (" + worksheetCount.ToString() + ") must be between 1 and 10"); + return !HasErrorState(); + } + + //------------------------------------------------------------------- + // Worksheet names. + public override List WorksheetNames + { + get + { + ClearErrorState(); + HashSet names = new HashSet(StringComparer.OrdinalIgnoreCase); + if (!HasErrorState() && !IsWorkbookValid()) + SetErrorState(Msg_WorkbookNotOpen); + if (!HasErrorState()) + { + for (int sheetPos = 1; sheetPos <= workbook.NumberOfSheets; sheetPos++) + names.Add(workbook.GetSheetName(sheetPos)); + } + return new List(names); + } + } + + //------------------------------------------------------------------- + // Add worksheet. + public override bool AddWorksheet(String name) + { + ClearErrorState(); + if (!HasErrorState() && !IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + CloseWorksheet(); + + // Determine name for sheet. + if (StringUtil.IsBlank(name)) + { + List sheetNames = WorksheetNames; + for (int sheetPos = 1; sheetPos <= workbook.NumberOfSheets; sheetPos++) + { + name = "Sheet " + sheetPos; + if (!sheetNames.ContainsCI(name)) + break; + } + } + + // Add a blank sheet. + worksheet = workbook.CreateSheet(name); + return IsWorksheetValid(); + } + + //------------------------------------------------------------------- + /// + /// Remove worksheet. + /// Worksheet 0 is the currently open sheet. + /// + public override bool RemoveWorksheet(uint worksheetPos = 0) + { + ClearErrorState(); + if (!IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + try + { + if (worksheetPos > WorksheetCount) + return SetErrorState("Worksheet position must be up to " + WorksheetCount); + if (worksheetPos < 1) + { + // Worksheet '0' is the current sheet. + if (worksheet != null) + worksheetPos = (uint)(workbook.GetSheetIndex(worksheet) + 1); + else return SetErrorState("Worksheet position '0' not available - no open worksheet."); + } + int worksheetIdx = ((int)worksheetPos) - 1; + + if (worksheet != null) + { + // Retrieve specified sheet to compare to currently open sheet. + ISheet sheet = workbook.GetSheetAt(worksheetIdx); + // Only close current sheet if it's about to be removed. + if (sheet.SheetName.EqualsCI(worksheet.SheetName)) + CloseWorksheet(); + } + workbook.RemoveSheetAt(worksheetIdx); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return true; + } + //------------------------------------------------------------------- // W o r k s h e e t //------------------------------------------------------------------- - public override bool IsWorksheetValid () + /// + /// Open worksheet by position (from 1). + /// Worksheet 0 is the currently open sheet or first if none open. + /// + public override bool OpenWorksheet(uint worksheetPos) { - return (worksheet != null && worksheet.WorksheetDoc != null); - } - public override uint WorksheetCount { get { return workbook.WorksheetCount; } } - private void PopulateWorksheetCount () - { - if (!IsWorkbookValid()) return; + ClearErrorState(); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); try { - // Count worksheets in workbook. - SaMsXmlElement nodeWorkbook = workbook.WorkbookDoc.GetRootElement(); - SaMsXmlElement nodeSheets = nodeWorkbook.GetFirstChildElement("sheets"); - workbook.WorksheetCount = (uint)nodeSheets.GetChildElements("sheet").Count; - } - catch (Exception exc) { HandleException("GetWorksheetCount", exc); } - } - public override bool SetWorksheetCount (int worksheetCount) - { - ClearErrorState(); - return SetErrorState("Modifying workbook not supported"); - } - public override bool AddWorksheet(String name) - { - return SetErrorState("Modifying workbook not supported"); - } - public override bool RemoveWorksheet(uint worksheetPos) - { - return SetErrorState("Modifying workbook not supported"); - } - public override bool OpenWorksheet (uint worksheetPos) - { - ClearErrorState(); - if (!workbook.IsValid()) return SetErrorState("Workbook is not open"); - if (worksheetPos > WorksheetCount) return SetErrorState("Invalid sheet specified"); - if (worksheetPos <= 0) worksheetPos = Math.Max(1, workbook.ActiveSheetPos); - CloseWorksheet(); - try - { - worksheet = new Worksheet(); - // Locate worksheet in workbook by 0-index. - SaMsXmlElement workbookElement = workbook.WorkbookDoc.GetRootElement(); - SaMsXmlElement sheetsElement = workbookElement.GetFirstChildElement("sheets"); - long sheetCount = 0; - foreach (SaMsXmlElement sheetElement in sheetsElement.GetChildElements("sheet")) + if (worksheetPos > WorksheetCount) + return SetErrorState("Worksheet position must be up to " + WorksheetCount); + if (worksheetPos < 1) { - sheetCount++; - if (sheetCount == worksheetPos) - { - // Extract the worksheet data. - uint sheetId = (uint)StringUtil.ToInt(sheetElement.GetAttributeValue("sheetId")); - String worksheetArchivePath = "xl/worksheets/sheet" + worksheetPos.ToString() + ".xml"; - using (MemoryStream worksheetFile = workbook.WorkbookArchive.ExtractFileToStream(worksheetArchivePath)) - { - if (worksheetFile == null) return SetErrorState("Failed to load worksheet data"); - worksheet.WorksheetDoc = new SaMsXmlDocument(); - if (worksheet.WorksheetDoc.LoadXml(worksheetFile)) - { - workbook.ActiveSheetPos = worksheetPos; - worksheet.WorksheetPos = worksheetPos; - worksheet.WorksheetId = sheetId; - worksheet.WorksheetArchivePath = worksheetArchivePath; - worksheet.WorksheetName = sheetElement.GetAttributeValue("name"); - worksheet.ActiveColPos = 1; - worksheet.ActiveRowPos = 1; - SaMsXmlElement nodeWorksheet = worksheet.WorksheetDoc.GetRootElement(); - worksheet.Nodes.Cols = nodeWorksheet.GetFirstChildElement("cols"); - worksheet.Nodes.ActiveCol = worksheet.Nodes.Cols.GetFirstChildElement("col"); - worksheet.Nodes.Rows = nodeWorksheet.GetFirstChildElement("sheetData"); - worksheet.Nodes.ActiveRow = worksheet.Nodes.Rows.GetFirstChildElement("row"); - workbook.ActiveSheetPos = worksheetPos; - } - else return SetErrorState("Failed to read data for worksheet " + worksheetPos.ToString()); - } - break; - } + // Worksheet '0' is the current sheet. + if (worksheet != null) + worksheetPos = (uint)(workbook.GetSheetIndex(worksheet) + 1); + else worksheetPos = 1; } - if (worksheet.WorksheetDoc == null) return SetErrorState("Unable to load worksheet " + worksheetPos.ToString()); - } - catch (Exception exc) { HandleException("OpenWorksheet", exc); } - return !HasErrorState(); - } - public override uint WorksheetPos { get { return workbook.ActiveSheetPos; } } - public override String WorksheetName - { - get { return ""; } - set { } - } - public override bool SetWorksheetAttr (SaMsExcelItemAttr attr) - { - return SetErrorState("Not supported"); - } - public override bool CloseWorksheet () - { - ClearErrorState(); + int worksheetIdx = ((int)worksheetPos) - 1; - if (worksheet != null) - { - try - { - if (worksheet.WorksheetDoc != null) worksheet.WorksheetDoc.Dispose(); worksheet.WorksheetDoc = null; - if (worksheet.Nodes.Cols != null) worksheet.Nodes.Cols.Dispose(); worksheet.Nodes.Cols = null; - if (worksheet.Nodes.ActiveCol != null) worksheet.Nodes.ActiveCol.Dispose(); worksheet.Nodes.ActiveCol = null; - if (worksheet.Nodes.Rows != null) worksheet.Nodes.Rows.Dispose(); worksheet.Nodes.Rows = null; - if (worksheet.Nodes.ActiveRow != null) worksheet.Nodes.ActiveRow.Dispose(); worksheet.Nodes.ActiveRow = null; - } - catch (Exception exc) { SetErrorState(ExceptionUtil.GetErrorStateMessage(exc)); } - worksheet = null; + // Open worksheet. + CloseWorksheet(); + worksheet = workbook.GetSheetAt(worksheetIdx); } - + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return !HasErrorState(); } //------------------------------------------------------------------- - // Worksheet Page Setup. - public override bool SetPageSetup (SaMsExcelPageSetup_Config pageSetupConfig) + /// Open worksheet by name (case and whitespace insensitive). + public override bool OpenWorksheet(String name) { - return SetErrorState("Not supported"); + ClearErrorState(); + name = StringUtil.TrimWhitespace(StringUtil.OnBlank(name)); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); + try + { + // Open worksheet. + for (int worksheetIdx = 0; worksheetIdx < workbook.NumberOfSheets; worksheetIdx++) + { + ISheet worksheetCandidate = workbook.GetSheetAt(worksheetIdx); + if (StringUtil.TrimWhitespace(worksheetCandidate.SheetName).EqualsCI(name)) + { + CloseWorksheet(); + worksheet = worksheetCandidate; + return true; + } + } + SetErrorState("Worksheet not found."); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } - public override bool SetPageBreak (uint col, uint row) + + //------------------------------------------------------------------- + // Close currently open worksheet. + public override bool CloseWorksheet() { - return SetErrorState("Not supported"); + ClearErrorState(); + if (!IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + if (worksheet != null) + worksheet = null; + return !HasErrorState(); + } + + //------------------------------------------------------------------- + public override bool IsWorksheetValid() + { + if (!IsWorkbookValid()) return false; + return worksheet != null; + } + + //------------------------------------------------------------------- + public override uint WorksheetPos { get { return (uint)(workbook.GetSheetIndex(worksheet) + 1); } } + public override String WorksheetName + { + get { return worksheet?.SheetName ?? ""; } + set { workbook.SetSheetName(workbook.GetSheetIndex(worksheet), value); } + } + public override bool SetWorksheetAttr(SaMsExcelStyleAttr attr) + { + ClearErrorState(); + if (!IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + if (!IsWorksheetValid()) + return SetErrorState("Worksheet is not open"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); + } + + //------------------------------------------------------------------- + // Worksheet Page Setup. + public override bool SetPageSetup(SaMsExcelPageSetup_Config pageSetupConfig) + { + ClearErrorState(); + if (!IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + if (!IsWorksheetValid()) + return SetErrorState("Worksheet is not open"); + //--------------------------------------------------------------- + // Page setup options: + // ActiveSheet.PageSetup + // .PrintTitleRows = "$4:$4" + // .PrintTitleColumns = "" + // .PrintArea = "" + // .LeftHeader = "" + // .CenterHeader = "" + // .RightHeader = "" + // .LeftFooter = "" + // .CenterFooter = "" + // .RightFooter = "" + // .LeftMargin = Application.InchesToPoints(0.708661417322835) + // .RightMargin = Application.InchesToPoints(0.708661417322835) + // .TopMargin = Application.InchesToPoints(0.748031496062992) + // .BottomMargin = Application.InchesToPoints(0.748031496062992) + // .HeaderMargin = Application.InchesToPoints(0.31496062992126) + // .FooterMargin = Application.InchesToPoints(0.31496062992126) + // .PrintHeadings = False + // .PrintGridlines = False + // .PrintComments = xlPrintNoComments + // .PrintQuality = 600 + // .CenterHorizontally = False + // .CenterVertically = False + // .Orientation = xlPortrait + // .Draft = False + // .PaperSize = xlPaperA4 + // .FirstPageNumber = xlAutomatic + // .Order = xlDownThenOver + // .BlackAndWhite = False + // .Zoom = 100 + // .PrintErrors = xlPrintErrorsDisplayed + // .OddAndEvenPagesHeaderFooter = False + // .DifferentFirstPageHeaderFooter = False + // .ScaleWithDocHeaderFooter = True + // .AlignMarginsHeaderFooter = True + // .EvenPage.LeftHeader.Text = "" + // .EvenPage.CenterHeader.Text = "" + // .EvenPage.RightHeader.Text = "" + // .EvenPage.LeftFooter.Text = "" + // .EvenPage.CenterFooter.Text = "" + // .EvenPage.RightFooter.Text = "" + // .FirstPage.LeftHeader.Text = "" + // .FirstPage.CenterHeader.Text = "" + // .FirstPage.RightHeader.Text = "" + // .FirstPage.LeftFooter.Text = "" + // .FirstPage.CenterFooter.Text = "" + // .FirstPage.RightFooter.Text = "" + //--------------------------------------------------------------- + // Negative values leave current value. + try + { + // Print setup. + IPrintSetup printSetup = worksheet.PrintSetup; + + if (pageSetupConfig.Orientation == SaXlPageOrientation.xlLandscape) + printSetup.Landscape = true; + else if (pageSetupConfig.Orientation == SaXlPageOrientation.xlPortrait) + printSetup.Landscape = false; + + // Headers and footers. + worksheet.Header.Left = pageSetupConfig.HeaderLeft; + worksheet.Header.Center = pageSetupConfig.HeaderCenter; + worksheet.Header.Right = pageSetupConfig.HeaderRight; + worksheet.Footer.Left = pageSetupConfig.FooterLeft; + worksheet.Footer.Center = pageSetupConfig.FooterCenter; + worksheet.Footer.Right = pageSetupConfig.FooterRight; + + // Margins. + if (pageSetupConfig.MarginLeft > 0) + worksheet.SetMargin(MarginType.LeftMargin, ToPoints(pageSetupConfig.MarginLeft, pageSetupConfig.Unit, false)); + if (pageSetupConfig.MarginRight > 0) + worksheet.SetMargin(MarginType.RightMargin, ToPoints(pageSetupConfig.MarginRight, pageSetupConfig.Unit, false)); + if (pageSetupConfig.MarginTop > 0) + worksheet.SetMargin(MarginType.TopMargin, ToPoints(pageSetupConfig.MarginTop, pageSetupConfig.Unit, false)); + if (pageSetupConfig.MarginBottom > 0) + worksheet.SetMargin(MarginType.BottomMargin, ToPoints(pageSetupConfig.MarginBottom, pageSetupConfig.Unit, false)); + if (pageSetupConfig.MarginHeader > 0) + worksheet.SetMargin(MarginType.HeaderMargin, ToPoints(pageSetupConfig.MarginHeader, pageSetupConfig.Unit, false)); + if (pageSetupConfig.MarginFooter > 0) + worksheet.SetMargin(MarginType.FooterMargin, ToPoints(pageSetupConfig.MarginFooter, pageSetupConfig.Unit, false)); + + // Print Title Rows. + if (pageSetupConfig.PrintTitleRowFrom > 0 && pageSetupConfig.PrintTitleRowTo > 0) + { + // Prefix with '$' to fix row value.(?) + worksheet.RepeatingRows = CellRangeAddress.ValueOf(pageSetupConfig.PrintTitleRowFrom + ":" + pageSetupConfig.PrintTitleRowTo); + } + else if (pageSetupConfig.PrintTitleRowFrom == 0 && pageSetupConfig.PrintTitleRowTo == 0) + worksheet.RepeatingRows = null; + + // Print Title Columns. + if (pageSetupConfig.PrintTitleColFrom > 0 && pageSetupConfig.PrintTitleColTo > 0) + worksheet.RepeatingColumns = CellRangeAddress.ValueOf(pageSetupConfig.PrintTitleColFrom + ":" + pageSetupConfig.PrintTitleColTo); + else if (pageSetupConfig.PrintTitleColFrom == 0 && pageSetupConfig.PrintTitleColTo == 0) + worksheet.RepeatingColumns = null; + + // Zoom & 'Fit to' options. + if (pageSetupConfig.Zoom > 0) + worksheet.SetZoom(pageSetupConfig.Zoom); + else if (pageSetupConfig.FitToPagesTall > 0 || pageSetupConfig.FitToPagesWide > 0) + { + worksheet.SetZoom(0); + worksheet.FitToPage = false; + printSetup.FitWidth = (short)Math.Min(0, pageSetupConfig.FitToPagesWide); + printSetup.FitHeight = (short)Math.Min(0, pageSetupConfig.FitToPagesTall); + } + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); + } + + //------------------------------------------------------------------- + // Page breaks. + public override bool SetPageBreak(uint col, uint row) + { + ClearErrorState(); + if (!IsWorkbookValid()) + return SetErrorState(Msg_WorkbookNotOpen); + if (!IsWorksheetValid()) + return SetErrorState("Worksheet is not open"); + try + { + worksheet.FitToPage = false; + worksheet.SetColumnBreak((int)(col - 1)); + worksheet.SetRowBreak((int)(row - 1)); + } + catch (Exception exc) + { + HandleException(SysUtil.Fn, exc); + } + return !HasErrorState(); } //------------------------------------------------------------------- // C o l u m n s //------------------------------------------------------------------- - public override uint GetActiveColPos () - { - return worksheet.ActiveColPos; - } - private SaMsXmlElement GetColElement (uint colPos) + public override uint ActiveColPos { get { return (uint)(worksheet.ActiveCell.Column + 1); } } + public override double GetColWidth(uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { ClearErrorState(); - try + if (!IsWorkbookValid()) SetErrorState(Msg_WorkbookNotOpen); + else if (!IsWorksheetValid()) SetErrorState("Worksheet is not open"); + else { - if (worksheet.ActiveColPos == colPos) return worksheet.Nodes.ActiveCol; - int colCount = 0; - foreach (SaMsXmlElement colElement in worksheet.Nodes.Cols.GetChildElements("Col")) + try { - if (++colCount == colPos) - { - if (worksheet.Nodes.ActiveCol != null) worksheet.Nodes.ActiveCol.Dispose(); - worksheet.ActiveColPos = colPos; - worksheet.Nodes.ActiveCol = colElement; - return colElement; - } + // Retrieve width (in points) from column. + double width = worksheet.GetColumnWidth((int)(colPos - 1)); + // Adjust for units. + if (unit == SaExcelLogicalUnit.Pixel) return PointsToPixels(width, false); + if (unit == SaExcelLogicalUnit.Cm) return PointsToCms(width); + if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(width); + return width; } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } } - catch (Exception exc) { HandleException("GetColElement", exc); } - return new SaMsXmlElement(null); - } - public override double GetColWidth (uint colPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) - { - ClearErrorState(); - try - { - // Retrieve width (in points) from column. - SaMsXmlNode nodeCol = GetColElement(colPos); - double width = nodeCol.GetAttributeValue("width").ToDouble(); - // Adjust for units. - if (unit == SaExcelLogicalUnit.Pixel) return PointsToPixels(width, false); - if (unit == SaExcelLogicalUnit.Cm) return PointsToCms(width); - if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(width); - return width; - } - catch (Exception exc) { HandleException("GetColWidth", exc); } return 0.0; } - public override bool SetColWidth (uint colPos, double fWidth, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + /// Default xcharacter width in pixels. + private static readonly double DEFAULT_CHARACTER_WIDTH = 7.001699924468994f; /* From Apache POI) */ + public override bool SetColWidth(uint colPos, double width, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { - return SetErrorState("Not supported"); + ClearErrorState(); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); + if (!IsWorksheetValid()) return SetErrorState("Worksheet is not open"); + try + { + // Adjust for units. + double widthPixels = ConvertLength(width, unit, SaExcelLogicalUnit.Pixel, false); + // POI units for column width are 1/256th default character width. + // Ref. NPOI.Util.Units + worksheet.SetColumnWidth((int)(colPos - 1), (int)Math.Ceiling(widthPixels * 256 / DEFAULT_CHARACTER_WIDTH)); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } //------------------------------------------------------------------- // R o w s //------------------------------------------------------------------- - public override uint GetActiveRowPos () + public override uint FirstRowPos { get { return (uint)(worksheet.FirstRowNum + 1); } } + public override uint LastRowPos { get { return (uint)(worksheet.LastRowNum + 1); } } + + //------------------------------------------------------------------- + private IRow GetRowByIdx(int rowIdx, bool create = false) { - return worksheet.ActiveRowPos; + IRow row = worksheet.GetRow(rowIdx); + if (row == null && create) + row = worksheet.CreateRow(rowIdx); + return row; } - SaMsXmlElement GetRowElement (uint rowPos) + + private IRow GetRowByPos(uint rowPos, bool create = false) + { + return GetRowByIdx((int)(rowPos - 1), create); + } + + public int ActiveRowIdx { get { return worksheet.ActiveCell.Row; } } + public override uint ActiveRowPos { get { return (uint)(ActiveRowIdx + 1); } } + + //------------------------------------------------------------------- + public override double GetRowHeight(uint rowPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { ClearErrorState(); - try + if (!IsWorkbookValid()) SetErrorState(Msg_WorkbookNotOpen); + else if (!IsWorksheetValid()) SetErrorState("Worksheet is not open"); + else { - if (worksheet.ActiveRowPos == rowPos) return worksheet.Nodes.ActiveRow; - foreach (SaMsXmlElement rowElement in worksheet.Nodes.Rows.GetChildElements("row")) + try { - if (rowElement.GetAttributeValue("r").ToLong() == rowPos) - { - if (worksheet.Nodes.ActiveRow != null) worksheet.Nodes.ActiveRow.Dispose(); - worksheet.ActiveRowPos = rowPos; - worksheet.Nodes.ActiveRow = rowElement; - return rowElement; - } + // Retrieve height (in points) from row. + IRow row = GetRowByPos(rowPos); + double height = row?.Height ?? 0.0; + // Adjust for units. + if (unit == SaExcelLogicalUnit.Pixel) return PointsToPixels(height, true); + if (unit == SaExcelLogicalUnit.Cm) return PointsToCms(height); + if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(height); + return height; } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } } - catch (Exception exc) { HandleException("GetRowElement", exc); } - return new SaMsXmlElement(null); - } - public override double GetRowHeight (uint rowPos, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) - { - ClearErrorState(); - try - { - // Retrieve height (in points) from row. - SaMsXmlElement rowElement = GetRowElement(rowPos); - double height = rowElement.GetAttributeValue("ht").ToDouble(); - // Adjust for units. - if (unit == SaExcelLogicalUnit.Pixel) return PointsToPixels(height, true); - if (unit == SaExcelLogicalUnit.Cm) return PointsToCms(height); - if (unit == SaExcelLogicalUnit.Inch) return PointsToInches(height); - return height; - } - catch (Exception exc) { HandleException("GetRowHeight", exc); } return 0.0; } - public override bool SetRowHeight (uint rowPos, double fHeight, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) + public override bool SetRowHeight(uint rowPos, double height, SaExcelLogicalUnit unit = SaExcelLogicalUnit.Point) { - return SetErrorState("Not supported"); + ClearErrorState(); + if (!IsWorkbookValid()) return SetErrorState(Msg_WorkbookNotOpen); + if (!IsWorksheetValid()) return SetErrorState("Worksheet is not open"); + try + { + // Adjust for units. + if (unit == SaExcelLogicalUnit.Pixel) height = PixelsToPoints(height, true); + if (unit == SaExcelLogicalUnit.Cm) height = CmsToPoints(height); + if (unit == SaExcelLogicalUnit.Inch) height = InchesToPoints(height); + // Set row height. + IRow row = GetRowByPos(rowPos, true); + row.Height = (short)height; + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } //------------------------------------------------------------------- - // R o w s - //------------------------------------------------------------------- - public override List GetRowDataValues (uint rowPos, int maxColumns = 0) + public override List GetRowValues(uint rowPos, int maxColumns = 0) { ClearErrorState(); List values = new List(); try { - SaMsXmlElement rowElement = GetRowElement(rowPos); - foreach (SaMsXmlElement cellElement in rowElement.GetChildElements("c")) + IRow row = GetRowByPos(rowPos); + if (row != null) { - // CellId may be needed in case cells are skipped in the data. - // Doesn't appear to be the case, even when previous cell is merged with next. - String cellId = cellElement.GetAttributeValue("r"); - values.Add(GetCellDataValue(cellElement)); - if (maxColumns > 0 && values.Count >= maxColumns) break; + foreach (ICell cell in row.Cells) + { + values.Add(GetCellDataValue(cell)); + if (maxColumns > 0 && values.Count >= maxColumns) break; + } } } - catch (Exception exc) { HandleException("GetRowDataValues", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return values; } //------------------------------------------------------------------- // C e l l s //------------------------------------------------------------------- - SaMsXmlElement GetCellElement (uint colPos, uint rowPos) + private ICell GetCell(uint colPos, uint rowPos, bool create = false) { ClearErrorState(); - String cellId = GetCellId(colPos,rowPos); - try - { - SaMsXmlElement rowElement = GetRowElement(rowPos); - foreach (SaMsXmlElement cellElement in rowElement.GetChildElements("c")) - { - if (cellElement.GetAttributeValue("r").EqualsCI(cellId)) - return cellElement; - } - } - catch (Exception exc) { HandleException("GetCellElement", exc); } - return new SaMsXmlElement(null); + IRow row = GetRowByPos(rowPos, create); + return GetCell(colPos, row, create); } - public override void SetActiveCell (uint colPos, uint rowPos) + + private ICell GetCell(uint colPos, IRow row, bool createBlank = false) { - // ToDo SetActiveCell. - SetErrorState("Not supported"); + ClearErrorState(); + ICell cell = row?.GetCell((int)(colPos - 1), createBlank ? MissingCellPolicy.CREATE_NULL_AS_BLANK : MissingCellPolicy.RETURN_NULL_AND_BLANK); + if (cell != null && (cell.CellStyle == null || cell.CellStyle.Index == 0)) + { + ICellStyle colStyle = worksheet.GetColumnStyle(cell.ColumnIndex); + if (colStyle.Index != 0) + cell.CellStyle = colStyle; + } + return cell; } + //------------------------------------------------------------------- - private bool IsCellFormula (uint colPos, uint rowPos) + public override void SetActiveCell(uint colPos, uint rowPos) { ClearErrorState(); - bool bIsFormula = false; try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - bIsFormula = !cellElement.GetAttributeValue("f").IsEmpty(); + worksheet.ActiveCell = new CellAddress((int)(rowPos - 1), (int)(colPos - 1)); + } + catch (Exception exc) + { + HandleException(SysUtil.Fn, exc); } - catch (Exception exc) { HandleException("IsCellFormula", exc); } - return bIsFormula; } - private bool IsCellNumeric (uint colPos, uint rowPos) + + //------------------------------------------------------------------- + private bool IsCellFormula(uint colPos, uint rowPos) { ClearErrorState(); try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); - return IsCellNumeric(cellElement, cellStyle); + ICell cell = GetCell(colPos, rowPos); + return IsCellFormula(cell); } - catch (Exception exc) { HandleException("IsCellNumeric", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return false; } - private bool IsCellNumeric (SaMsXmlElement cellElement) + private bool IsCellFormula(ICell cell) { - ClearErrorState(); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); - return IsCellNumeric(cellElement, cellStyle); + return cell != null && cell.CellType == NPOI.SS.UserModel.CellType.Formula; } - private bool IsCellNumeric (SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) + private bool IsCellNumeric(uint colPos, uint rowPos) { ClearErrorState(); - return (cellElement.GetAttributeValue("t").IsEmpty() && !IsDateTimeStyle(cellStyle.NumFmtCode)); - } - private bool IsCellDate (uint colPos, uint rowPos) - { - ClearErrorState(); - bool bIsDate = false; try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); - return IsCellDate(cellElement, cellStyle); + ICell cell = GetCell(colPos, rowPos); + return IsCellNumeric(cell); } - catch (Exception exc) { HandleException("IsCellDate", exc); } - return bIsDate; + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return false; } - private bool IsCellDate (SaMsXmlElement cellElement) + private bool IsCellNumeric(ICell cell) { - return IsCellDate(cellElement, GetCellStyle(cellElement)); + return cell != null && cell.CellType == NPOI.SS.UserModel.CellType.Numeric; } - private bool IsCellDate (SaMsXmlElement cellElement, SaMsExcelStyle cellStyle) - { - ClearErrorState(); - return IsDateTimeStyle(cellStyle.NumFmtCode); - } - //------------------------------------------------------------------- - public override String GetCellFormula (uint colPos, uint rowPos) + private bool IsCellDate(uint colPos, uint rowPos) { ClearErrorState(); try { - SaMsXmlElement cellElement = GetCellElement(colPos,rowPos); - String formula = cellElement.GetAttributeValue("f"); - if (formula.IsEmpty()) - formula = GetCellText(cellElement, true); + ICell cell = GetCell(colPos, rowPos); + return IsCellDate(cell); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return false; + } + private bool IsCellDate(ICell cell) + { + return cell != null && NPOI.SS.UserModel.DateUtil.IsCellDateFormatted(cell); + } + + //------------------------------------------------------------------- + public override String GetCellFormula(uint colPos, uint rowPos) + { + ClearErrorState(); + try + { + ICell cell = GetCell(colPos, rowPos); + String formula = ""; + if (cell != null) + { + if (IsCellFormula(cell)) + formula = cell.CellFormula; + if (StringUtil.IsEmpty(formula) && cell != null) + formula = GetCellText(cell, true); + } return formula; } - catch (Exception exc) { HandleException("GetCellFormula", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return ""; } - private String GetCellText (SaMsXmlElement cellElement, bool trimValue = true) + + public override String GetCellText(uint colPos, uint rowPos, bool trimWhitespace = true) + { + ClearErrorState(); + try + { + ICell cell = GetCell(colPos, rowPos); + return cell != null ? GetCellText(cell, true) : ""; + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return ""; + } + + private String GetCellText(ICell cell, bool trimValue = true) { ClearErrorState(); String value = ""; try { - if (cellElement.GetAttributeValue("t").EqualsCI("s")) - value = GetSharedString(cellElement); - else - value = cellElement.GetChildText("v"); + value = cell.ToString(); if (trimValue) value.TrimAll(); } - catch (Exception exc) { HandleException("GetCellText", exc); } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } return value; } - public override DataValue GetCellDataValue (uint colPos, uint rowPos) + + public override DataValue GetCellValue(uint colPos, uint rowPos) { - return GetCellDataValue(GetCellElement(colPos, rowPos), false); + return GetCellDataValue(GetCell(colPos, rowPos), false); } - private DataValue GetCellDataValue (SaMsXmlElement cellElement, bool trimTextValue = true) + private DataValue GetCellDataValue(ICell cell, bool trimTextValue = true) { ClearErrorState(); try { - SaMsExcelStyle cellStyle = GetCellStyle(cellElement); - if (cellElement.GetAttributeValue("t").EqualsCI("s")) + if (IsCellDate(cell)) + return new DataValue(cell.DateCellValue); + if (IsCellNumeric(cell)) { - String value = GetSharedString(cellElement); - if (trimTextValue) value = value.TrimAll(); - return new DataValue(value); - } - else - { - String value = cellElement.GetChildText("v"); - if (IsDateTimeStyle(cellStyle.NumFmtCode)) - return new DataValue(DateTime.FromOADate(value.ToDouble())); - if (IsCellNumeric(cellElement, cellStyle)) - { - if (value.IsNumeric()) - return new DataValue(value.ToLong()); - if (value.IsDecimal()) - { -#if DEBUG - //if (StringUtil.ToInt(value) > 600) Debugger.Break(); -#endif - return new DataValue(value.ToDecimal()); - } - } - if (trimTextValue) value = value.TrimAll(); - return new DataValue(value); + double value = cell.NumericCellValue; + if (Math.Floor(value) == value) + return new DataValue((long)value); + else + return new DataValue(value); } + return new DataValue(cell.StringCellValue); + } + catch (Exception exc) + { + HandleException(SysUtil.Fn, exc); } - catch (Exception exc) { HandleException("GetCellDataValue", exc); } return new DataValue(); } + //------------------------------------------------------------------- - // Shared string for cell. - private String GetSharedString (SaMsXmlElement nodeElement) + public override bool SetCellFormula(uint colPos, uint rowPos, String value, SaMsExcelStyleAttr attr) { - ClearErrorState(); try { - SaMsXmlElement sharedStringElement = GetSharedStringElement(nodeElement); - return sharedStringElement.GetChildText("t"); + ICell cell = GetCell(colPos, rowPos, true); + cell.CellFormula = value; + // Set cell attributes (style etc). + SaMsExcelRange range = new SaMsExcelRange(colPos, rowPos); + SetRangeAttr(range, attr); } - catch (Exception exc) { HandleException("GetSharedString", exc); } - return ""; + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } - private SaMsXmlElement GetSharedStringElement (SaMsXmlElement cellElement) + + public override bool SetCellValue(uint colPos, uint rowPos, DataValue value, SaMsExcelStyleAttr attr) { - ClearErrorState(); - if (workbook.SharedStringsDoc == null || cellElement == null || !cellElement.Valid) return null; - if (!cellElement.GetAttributeValue("t").EqualsCI("s") || !cellElement.GetChildText("v").IsNumeric()) return null; - // Shared strings are indexed from 0. - long sharedStringIdx = -1; try { - long iIdx = cellElement.GetChildText("v").ToLong(); - SaMsXmlElement sstElement = workbook.SharedStringsDoc.GetRootElement(); - foreach (SaMsXmlElement siElement in sstElement.GetChildElements("si")) + ICell cell = GetCell(colPos, rowPos, true); + SetCellValue(cell, value); + // Set cell attributes (style etc). + SaMsExcelRange range = new SaMsExcelRange(colPos, rowPos); + SetRangeAttr(range, attr); + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); + } + + //------------------------------------------------------------------- + private void SetCellValue(ICell cell, Object value) + { + if (value == null) + cell.SetBlank(); + else if (value is bool) + cell.SetCellValue((bool)value); + else if (value is short) + cell.SetCellValue((short)value); + else if (value is int) + cell.SetCellValue((int)value); + else if (value is long) + cell.SetCellValue((long)value); + else if (value is float) + cell.SetCellValue((float)value); + else if (value is double) + cell.SetCellValue((double)value); + else if (value is DateTime) + cell.SetCellValue((DateTime)value); + else + cell.SetCellValue(value.ToString()); + } + + private void SetCellValue(ICell cell, DataValue value) + { + if (value == null) + cell.SetBlank(); + // Populate cell value based on value type. + switch (value.GetDataType()) + { + case DataValue.DataType.Bool: + cell.SetCellValue(value.AsBool()); + break; + case DataValue.DataType.Long: + cell.SetCellValue(value.AsLong()); + break; + case DataValue.DataType.Double: + cell.SetCellValue(value.AsDouble()); + break; + case DataValue.DataType.Decimal: + cell.SetCellValue((double)value.AsDecimal()); + break; + case DataValue.DataType.Date: + cell.SetCellValue(value.AsDate()); + break; + case DataValue.DataType.Time: + cell.SetCellValue(value.AsTime()); + break; + case DataValue.DataType.DateTime: + cell.SetCellValue(value.AsDateTime()); + break; + default: + cell.SetCellValue(value.ToString()); + break; + } + } + + //------------------------------------------------------------------- + public override bool SetRowFormulas(uint colPosStart, uint rowPos, List values) + { + try + { + IRow row = GetRowByPos(rowPos, true); + for (Math.Max(1, colPosStart); colPosStart <= values.Count; colPosStart++) { - if (++sharedStringIdx == iIdx) - return siElement; + Object value = values[(int)(colPosStart - 1)]; + ICell cell = GetCell(colPosStart, row, true); + SetCellValue(cell, value); } } - catch (Exception exc) { HandleException("GetSharedStringNode", exc); } - return new SaMsXmlElement(null); + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); } + + public override bool SetRowValues(uint colPosStart, uint rowPos, List values) + { + try + { + IRow row = GetRowByPos(rowPos, true); + for (int valueIdx = 0; valueIdx < values.Count; valueIdx++) + { + uint colPos = (uint)Math.Max(1, colPosStart + valueIdx); + Object value = values[valueIdx]; + ICell cell = GetCell(colPos, row, true); + SetCellValue(cell, value); + } + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); + } + //------------------------------------------------------------------- - public override bool SetCellFormula (uint colPos, uint rowPos, String value) + private void EvaluateAllFormulaCells() { - return SetErrorState("Not supported"); - } - public override bool SetCellFormula (uint colPos, uint rowPos, String value, SaMsExcelItemAttr attr) - { - return SetErrorState("Not supported"); - } - public override bool SetRowFormulas (uint colPosStart, uint rowPos, List values) - { - return SetErrorState("Not supported"); - } - public override bool SetRangeFormulas (SaMsExcelRange range, List values) - { - return SetErrorState("Not supported"); + if (workbook is XSSFWorkbook) + XSSFFormulaEvaluator.EvaluateAllFormulaCells(workbook); + else + HSSFFormulaEvaluator.EvaluateAllFormulaCells(workbook); } //------------------------------------------------------------------- // C e l l S t y l e //------------------------------------------------------------------- - private String GetBuiltInStyleFormat (int numFmtId) + private String GetBuiltInStyleFormat(int numFmtId) { // Ref. http://stackoverflow.com/questions/4730152/what-indicates-an-office-open-xml-cell-contains-a-date-time-value switch (numFmtId) @@ -736,97 +1051,7 @@ namespace Sarand.Net.Standard.Proc.Excel } return ""; } - private SaMsExcelStyle GetCellStyle (SaMsXmlElement cellElement) - { - ClearErrorState(); - SaMsExcelStyle style = new SaMsExcelStyle(); - if (workbook.SharedStringsDoc == null || cellElement == null || !cellElement.Valid || !cellElement.GetAttributeValue("s").IsNumeric()) return style; - // Styles are indexed from 0. - long styleIdx = -1; - try - { - long cellStyleIdx = cellElement.GetAttributeValue("s").ToLong(); - SaMsXmlElement styleSheetElement = workbook.SharedStringsDoc.GetRootElement(); - SaMsXmlElement cellXfsElement = styleSheetElement.GetFirstChildElement("cellXfs"); - foreach (SaMsXmlElement xfElement in styleSheetElement.GetChildElements("xf")) - { - if (++styleIdx == cellStyleIdx) - { - // Construct style. - style.NumFmtId = (int)xfElement.GetAttributeValue("numFmtId").ToLong(); - style.FontId = (int)xfElement.GetAttributeValue("fontId").ToLong(); - style.FillId = (int)xfElement.GetAttributeValue("fillId").ToLong(); - style.BorderId = (int)xfElement.GetAttributeValue("borderId").ToLong(); - style.XfId = (int)xfElement.GetAttributeValue("xfId").ToLong(); - style.QuotePrefix = xfElement.GetAttributeValue("quotePrefix").ToBool(); - style.PivotButton = xfElement.GetAttributeValue("pivotButton").ToBool(); - style.ApplyNumberFormat = xfElement.GetAttributeValue("applyNumberFormat").ToBool(); - style.ApplyFont = xfElement.GetAttributeValue("applyFont").ToBool(); - style.ApplyFill = xfElement.GetAttributeValue("applyFill").ToBool(); - style.ApplyBorder = xfElement.GetAttributeValue("applyBorder").ToBool(); - style.ApplyAlignment = xfElement.GetAttributeValue("applyAlignment").ToBool(); - style.ApplyProtection = xfElement.GetAttributeValue("applyProtection").ToBool(); - // Alignment. - SaMsXmlNode nodeAlignment = xfElement.GetFirstChildElement("alignment"); - String sAlignHorizontal = nodeAlignment.GetAttributeValue("horizontal"); - if (sAlignHorizontal.EqualsCI("center")) style.AlignHorizontal = XlHAlign.xlHAlignCenter; - else if (sAlignHorizontal.EqualsCI("centerContinuous")) style.AlignHorizontal = XlHAlign.xlHAlignCenterAcrossSelection; - else if (sAlignHorizontal.EqualsCI("distributed")) style.AlignHorizontal = XlHAlign.xlHAlignDistributed; - else if (sAlignHorizontal.EqualsCI("fill")) style.AlignHorizontal = XlHAlign.xlHAlignFill; - else if (sAlignHorizontal.EqualsCI("justify")) style.AlignHorizontal = XlHAlign.xlHAlignJustify; - else if (sAlignHorizontal.EqualsCI("left")) style.AlignHorizontal = XlHAlign.xlHAlignLeft; - else if (sAlignHorizontal.EqualsCI("right")) style.AlignHorizontal = XlHAlign.xlHAlignRight; - String sAlignVertical = nodeAlignment.GetAttributeValue("vertical"); - if (sAlignVertical.EqualsCI("center")) style.AlignVertical = XlVAlign.xlVAlignCenter; - else if (sAlignVertical.EqualsCI("top")) style.AlignVertical = XlVAlign.xlVAlignTop; - else if (sAlignVertical.EqualsCI("bottom")) style.AlignVertical = XlVAlign.xlVAlignBottom; - else if (sAlignVertical.EqualsCI("distributed")) style.AlignVertical = XlVAlign.xlVAlignDistributed; - else if (sAlignVertical.EqualsCI("justify")) style.AlignVertical = XlVAlign.xlVAlignJustify; - // Number format. - if (style.ApplyNumberFormat) - { - if (style.NumFmtId >= 164) - { - SaMsXmlElement numFmtsElement = styleSheetElement.GetFirstChildElement("NumFmts"); - foreach (SaMsXmlElement numFmtElement in numFmtsElement.GetChildElements("numFmt")) - { - if (numFmtElement.GetAttributeValue("numFmtId").ToLong() == style.NumFmtId) - { - style.NumFmtCode = numFmtElement.GetAttributeValue("formatCode"); - break; - } - } - } - else style.NumFmtCode = GetBuiltInStyleFormat(style.NumFmtId); - } - // Font. - if (style.ApplyFont) - { - } - // Fill. - if (style.ApplyFill) - { - } - // Borders. - if (style.ApplyBorder) - { - } - // Alignment. - if (style.ApplyAlignment) - { - } - // Protection. - if (style.ApplyProtection) - { - } - break; - } - } - } - catch (Exception exc) { HandleException("GetStyleNode", exc); } - return style; - } - private bool IsDateStyle (String numFormat) + private bool IsDateStyle(String numFormat) { // Ref. http://stackoverflow.com/questions/19034805/how-to-distinguish-inline-numbers-from-ole-automation-date-numbers-in-openxml-sp/19582685#19582685 if (IsTimeStyle(numFormat)) return false; @@ -835,7 +1060,7 @@ namespace Sarand.Net.Standard.Proc.Excel if (numFormat.Contains("y")) return true; return false; } - private bool IsTimeStyle (String numFormat) + private bool IsTimeStyle(String numFormat) { if (numFormat.Contains("h")) return true; if (numFormat.Contains(":mm")) return true; @@ -843,41 +1068,726 @@ namespace Sarand.Net.Standard.Proc.Excel if (numFormat.Contains(":ss")) return true; return false; } - private bool IsDateTimeStyle (String numFormat) + private bool IsDateTimeStyle(String numFormat) { return (IsDateStyle(numFormat) || IsTimeStyle(numFormat)); } //------------------------------------------------------------------- // Cell attributes. - public override bool SetRangeAttr (SaMsExcelRange range, SaMsExcelItemAttr attr) + public override bool SetRangeAttr(SaMsExcelRange range, SaMsExcelStyleAttr attr) { - return SetErrorState("Not supported"); + if (attr == null) return false; + try + { + // Apply style to cells. + for (uint rowPos = range.RowFromPos; rowPos <= range.RowToPos; rowPos++) + { + IRow row = null; + if (rowPos > 0) + row = GetRowByPos(rowPos, true); + for (uint colPos = range.ColFromPos; colPos <= range.ColToPos; colPos++) + { + if (rowPos < 1 && colPos < 1) + { + // Style is to be aplied to entire sheet. + } + else if (rowPos < 1) + { + // Style is to be applied to entire column. + ICellStyle colStyle = worksheet.GetColumnStyle((int)(colPos - 1)); + if (colStyle != null && colStyle.Index != 0) + { + // Column already has its own specific stlying. + ApplyAttrToCellStyle(attr, colStyle); + worksheet.SetDefaultColumnStyle((int)(colPos - 1), colStyle); + } + else + { + // Create specific styling for column. + colStyle = CloneCellStyle(colStyle); + ApplyAttrToCellStyle(attr, colStyle); + worksheet.SetDefaultColumnStyle((int)(colPos - 1), colStyle); + } + // Apply style to existing cells in column. + for (uint colRowPos = FirstRowPos; colRowPos <= LastRowPos; colRowPos++) + { + ICell cell = GetCell(colPos, colRowPos, false); + ApplyAttrToCell(attr, cell); + } + } + else if (colPos < 1) + { + // Style is to be applied to entire row. + if (row != null) + { + for (int cellColIdx = row.FirstCellNum; cellColIdx < row.LastCellNum; cellColIdx++) + { + ICell cell = row.GetCell(cellColIdx, MissingCellPolicy.RETURN_NULL_AND_BLANK); + if (cell == null) continue; + ApplyAttrToCell(attr, cell); + } + } + } + else + { + // Apply style to cell. + ICell cell = GetCell(colPos, rowPos, true); + ApplyAttrToCell(attr, cell); + } + } + } + } + catch (Exception exc) { HandleException(SysUtil.Fn, exc); } + return !HasErrorState(); + } + + //------------------------------------------------------------------- + private ICellStyle CloneCellStyle(ICellStyle cellStyle) + { + ICellStyle cellStyleNew = workbook.CreateCellStyle(); + cellStyleNew.CloneStyleFrom(cellStyle); + return cellStyleNew; + } + + //------------------------------------------------------------------- + private void ApplyAttrToCell(SaMsExcelStyleAttr attr, ICell cell) + { + if (cell == null) return; + Dictionary styleProperties = GetStyleProperties(attr); + if (attr.FontAttr.IsSpecified()) + PopulateFontStyleProperties(styleProperties, attr, cell.CellStyle.GetFont(workbook)); + if (styleProperties.IsEmpty()) return; + CellUtil.SetCellStyleProperties(cell, styleProperties); + } + + private void ApplyAttrToCellStyle(SaMsExcelStyleAttr attr, ICellStyle cellStyle) + { + Dictionary styleProperties = GetStyleProperties(attr); + if (attr.FontAttr.IsSpecified()) + PopulateFontStyleProperties(styleProperties, attr, cellStyle.GetFont(workbook)); + ApplyStylingProperties(cellStyle, styleProperties); + } + + private Dictionary GetStyleProperties(SaMsExcelStyleAttr attr) + { + // Style properties to change via CellUtil (when cell specified). + Dictionary styleProperties = new Dictionary(); + + if (attr.MergeCells ?? false) + { + // Merge cells. + } + + if (!StringUtil.IsBlank(attr.DataFormat)) + { + // Data format. + IDataFormat dataFormat = workbook.CreateDataFormat(); + short dataFormatIdx = dataFormat.GetFormat(attr.DataFormat); + if (dataFormatIdx >= 0) + styleProperties.Add(CellUtil.DATA_FORMAT, dataFormatIdx); + } + + // Alignment. + if (attr.HorizontalAlignment != null) + { + styleProperties.Add(CellUtil.ALIGNMENT, GetPoiHorizontalAlignment((XlHAlign)attr.HorizontalAlignment)); + } + + // Vertical alignment. + if (attr.VerticalAlignment != null) + { + styleProperties.Add(CellUtil.VERTICAL_ALIGNMENT, GetPoiVerticalAlignment((XlVAlign)attr.VerticalAlignment)); + } + + // Indentation. + if (attr.IndentLevel != null) + { + styleProperties.Add(CellUtil.INDENTION, (short)attr.IndentLevel); + } + + // Wrap text. + if (attr.WrapText ?? false) + { + styleProperties.Add(CellUtil.WRAP_TEXT, (bool)attr.WrapText); + } + + // Interior colour and pattern. + if (attr.ForegroundColourIndex != SaXlColourIndex.NotSpecified) + { + styleProperties.Add(CellUtil.FILL_FOREGROUND_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.ForegroundColourIndex, IndexedColors.White.Index)); + styleProperties.Add(CellUtil.FILL_PATTERN, FillPattern.SolidForeground); + } + else if (attr.ForegroundColourRGB != null) + { + // Convert RGB colour to palette index. + short? interiorColourIndex = GetCustomerColourIndex((int)attr.ForegroundColourRGB); + if (interiorColourIndex != null) + { + styleProperties.Add(CellUtil.FILL_PATTERN, FillPattern.SolidForeground); + styleProperties.Add(CellUtil.FILL_FOREGROUND_COLOR, interiorColourIndex); + } + } + if (attr.InteriorPattern != SaXlPattern.NotSpecified) + { + styleProperties.Add(CellUtil.FILL_PATTERN, (FillPattern)attr.InteriorPattern); + } + if (attr.BackgroundColourIndex != SaXlColourIndex.NotSpecified) + { + styleProperties.Add(CellUtil.FILL_BACKGROUND_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.BackgroundColourIndex, IndexedColors.White.Index)); + } + else if (attr.BackgroundPatternColour != null) + { + // Convert RGB colour to palette index. + short? interiorPatternColourIndex = GetCustomerColourIndex((int)attr.BackgroundPatternColour); + if (interiorPatternColourIndex != null) + styleProperties.Add(CellUtil.FILL_BACKGROUND_COLOR, (short)interiorPatternColourIndex); + } + + // Borders. + if (attr.BorderTop.IsSpecified) + { + if (attr.BorderTop.Weight != SaXlBorderWeight.NotSpecified || attr.BorderTop.LineStyle != SaXlLineStyle.NotSpecified) + styleProperties.Add(CellUtil.BORDER_TOP, GetBorderStyle(attr.BorderTop)); + if (attr.BorderTop.Color != null) + styleProperties.Add(CellUtil.TOP_BORDER_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.BorderTop.Color.Value, IndexedColors.Black.Index)); + else if (attr.BorderTop.ColorIndex != SaXlColourIndex.NotSpecified) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.BorderTop.ColorIndex); + if (colourIndex != null) + styleProperties.Add(CellUtil.TOP_BORDER_COLOR, (short)colourIndex); + } + } + if (attr.BorderBottom.IsSpecified) + { + if (attr.BorderBottom.Weight != SaXlBorderWeight.NotSpecified || attr.BorderBottom.LineStyle != SaXlLineStyle.NotSpecified) + styleProperties.Add(CellUtil.BORDER_BOTTOM, GetBorderStyle(attr.BorderBottom)); + if (attr.BorderBottom.Color != null) + styleProperties.Add(CellUtil.BOTTOM_BORDER_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.BorderBottom.Color, IndexedColors.Black.Index)); + else if (attr.BorderBottom.ColorIndex != SaXlColourIndex.NotSpecified) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.BorderBottom.ColorIndex); + if (colourIndex != null) + styleProperties.Add(CellUtil.BOTTOM_BORDER_COLOR, (short)colourIndex); + } + } + if (attr.BorderLeft.IsSpecified) + { + if (attr.BorderLeft.Weight != SaXlBorderWeight.NotSpecified || attr.BorderLeft.LineStyle != SaXlLineStyle.NotSpecified) + styleProperties.Add(CellUtil.BORDER_LEFT, GetBorderStyle(attr.BorderLeft)); + if (attr.BorderLeft.Color != null) + styleProperties.Add(CellUtil.LEFT_BORDER_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.BorderLeft.Color, IndexedColors.Black.Index)); + else if (attr.BorderLeft.ColorIndex != SaXlColourIndex.NotSpecified) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.BorderLeft.ColorIndex); + if (colourIndex != null) + styleProperties.Add(CellUtil.LEFT_BORDER_COLOR, (short)colourIndex); + } + } + if (attr.BorderRight.IsSpecified) + { + if (attr.BorderRight.Weight != SaXlBorderWeight.NotSpecified || attr.BorderRight.LineStyle != SaXlLineStyle.NotSpecified) + styleProperties.Add(CellUtil.BORDER_RIGHT, GetBorderStyle(attr.BorderRight)); + if (attr.BorderRight.Color != null) + styleProperties.Add(CellUtil.RIGHT_BORDER_COLOR, GetPoiColourIndex((SaXlColourIndex)attr.BorderRight.Color, IndexedColors.Black.Index)); + else if (attr.BorderRight.ColorIndex != SaXlColourIndex.NotSpecified) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.BorderRight.ColorIndex); + if (colourIndex != null) + styleProperties.Add(CellUtil.RIGHT_BORDER_COLOR, (short)colourIndex); + } + } + return styleProperties; + } + + private HorizontalAlignment GetPoiHorizontalAlignment(XlHAlign hAlign) + { + switch (hAlign) + { + case XlHAlign.xlHAlignGeneral: return HorizontalAlignment.General; + case XlHAlign.xlHAlignLeft: return HorizontalAlignment.Left; + case XlHAlign.xlHAlignCenter: return HorizontalAlignment.Center; + case XlHAlign.xlHAlignRight: return HorizontalAlignment.Right; + case XlHAlign.xlHAlignJustify: return HorizontalAlignment.Justify; + case XlHAlign.xlHAlignCenterAcrossSelection: return HorizontalAlignment.CenterSelection; + case XlHAlign.xlHAlignFill: return HorizontalAlignment.Fill; + case XlHAlign.xlHAlignDistributed: return HorizontalAlignment.Distributed; + } + return HorizontalAlignment.General; + } + + private VerticalAlignment GetPoiVerticalAlignment(XlVAlign vAlign) + { + switch (vAlign) + { + case XlVAlign.xlVAlignTop: return VerticalAlignment.Top; + case XlVAlign.xlVAlignJustify: return VerticalAlignment.Justify; + case XlVAlign.xlVAlignDistributed: return VerticalAlignment.Distributed; + case XlVAlign.xlVAlignCenter: return VerticalAlignment.Center; + case XlVAlign.xlVAlignBottom: return VerticalAlignment.Bottom; + } + return VerticalAlignment.None; + } + + private void PopulateFontStyleProperties(Dictionary styleProperties, SaMsExcelStyleAttr attr, IFont fontBase) + { + int fontIndex = GetFontIndex(attr, fontBase); + if (fontIndex >= 0) + styleProperties.Add(CellUtil.FONT, (short)fontIndex); + } + + private short GetFontIndex(SaMsExcelStyleAttr attr, IFont fontBase) + { + if (!attr.FontAttr.IsSpecified()) return -1; + + // Base font attributes. + bool bold = fontBase.IsBold; + short color = fontBase.Color; + double heightInPoints = fontBase.FontHeightInPoints; + String name = fontBase.FontName; + bool italic = fontBase.IsItalic; + bool strikeout = fontBase.IsStrikeout; + FontSuperScript typeOffset = fontBase.TypeOffset; + FontUnderlineType underline = fontBase.Underline; + + if (attr.FontAttr.Bold ?? false) + bold = true; + if (attr.FontAttr.ColourIndex != SaXlColourIndex.NotSpecified) + { + color = GetPoiColourIndex((SaXlColourIndex)attr.FontAttr.ColourIndex, IndexedColors.Black.Index); + } + if (attr.FontAttr.ColourRGB != null) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.FontAttr.ColourRGB); + if (colourIndex != null) + color = (short)attr.FontAttr.ColourRGB; + } + if (attr.FontAttr.HeightInPoints != null && attr.FontAttr.HeightInPoints >= FontHeightInPointsMin && attr.FontAttr.HeightInPoints <= FontHeightInPointsMax) + heightInPoints = (double)attr.FontAttr.HeightInPoints; + if (!StringUtil.IsBlank(attr.FontAttr.Name)) + name = attr.FontAttr.Name; + if (attr.FontAttr.Strikethrough ?? false) + strikeout = true; + if (attr.FontAttr.Superscript ?? false) + typeOffset = FontSuperScript.Super; + if (attr.FontAttr.Subscript ?? false) + typeOffset = FontSuperScript.Sub; + if (attr.FontAttr.Outline ?? false) + { + //font.OutlineFont = (short)attr.FontAttr.OutlineFont; + } + if (attr.FontAttr.Shadow ?? false) + { + //font.Shadow = true; + } + if (attr.FontAttr.Underline != null) + underline = (FontUnderlineType)attr.FontAttr.Underline; + + // Check if equivalent font is already available. + IFont fontTarget = workbook.FindFont(bold, color, (short)(heightInPoints * 20), name, italic, strikeout, typeOffset, underline); + if (fontTarget == null) + { + // Create required font. + fontTarget = workbook.CreateFont(); + fontTarget.IsBold = bold; + fontTarget.Color = color; + fontTarget.FontHeightInPoints = heightInPoints; + fontTarget.FontName = name; + fontTarget.IsItalic = italic; + fontTarget.IsStrikeout = strikeout; + fontTarget.TypeOffset = typeOffset; + fontTarget.Underline = underline; + } + if (fontTarget != null) + return fontTarget.Index; + SaLogger.Warn(SysUtil.Fn + " Failed to identify required font."); + return -1; + } + + private void ApplyFontAttr(IFont font, SaMsExcelStyleAttr attr) + { + if (attr.FontAttr.Bold ?? false) + font.IsBold = true; + if (attr.FontAttr.ColourIndex != SaXlColourIndex.NotSpecified) + { + font.Color = GetPoiColourIndex(attr.FontAttr.ColourIndex, IndexedColors.Black.Index); + } + if (attr.FontAttr.ColourRGB != null) + { + // Convert RGB colour to palette index. + short? colourIndex = GetCustomerColourIndex((int)attr.FontAttr.ColourRGB); + if (colourIndex != null) + font.Color = (short)attr.FontAttr.ColourRGB; + } + if (attr.FontAttr.HeightInPoints != null && attr.FontAttr.HeightInPoints >= FontHeightInPointsMin && attr.FontAttr.HeightInPoints <= FontHeightInPointsMax) + font.FontHeightInPoints = (double)attr.FontAttr.HeightInPoints; + if (!StringUtil.IsBlank(attr.FontAttr.Name)) + font.FontName = attr.FontAttr.Name; + if (attr.FontAttr.Strikethrough ?? false) + font.IsStrikeout = true; + if (attr.FontAttr.Superscript ?? false) + font.TypeOffset = FontSuperScript.Super; + if (attr.FontAttr.Subscript ?? false) + font.TypeOffset = FontSuperScript.Sub; + if (attr.FontAttr.Outline ?? false) + { + //font.OutlineFont = (short)attr.FontAttr.OutlineFont; + } + if (attr.FontAttr.Shadow ?? false) + { + //font.Shadow = true; + } + if (attr.FontAttr.Underline != null) + font.Underline = (FontUnderlineType)attr.FontAttr.Underline; + } + + //------------------------------------------------------------------- + private short GetPoiColourIndex(SaXlColourIndex colourIndex, short defaultPoiColorIndex) + { + IndexedColors poiColor = IndexedColors.ValueOf(colourIndex.GetName()); + if (poiColor != null) return poiColor.Index; + return defaultPoiColorIndex; + } + + private short? GetCustomerColourIndex(int rgbColour) + { + if (workbook is HSSFWorkbook) + { + HSSFPalette palette = ((HSSFWorkbook)workbook).GetCustomPalette(); + byte red = (byte)((rgbColour >> 0) & 0xff); + byte green = (byte)((rgbColour >> 8) & 0xff); + byte blue = (byte)((rgbColour >> 16) & 0xff); + // Get the color which most closely matches the color you want to use + return palette.FindSimilarColor(red, green, blue).Indexed; + } + else if (workbook is XSSFWorkbook) + { + StylesTable stylesTable = ((XSSFWorkbook)workbook).GetStylesSource(); + byte red = (byte)((rgbColour >> 0) & 0xff); + byte green = (byte)((rgbColour >> 8) & 0xff); + byte blue = (byte)((rgbColour >> 16) & 0xff); + IIndexedColorMap colorMap = stylesTable.GetIndexedColors(); + for (int styleIdx = 0; styleIdx < stylesTable.NumCellStyles; styleIdx++) + { + ICellStyle style = stylesTable.GetStyleAt(styleIdx); + if (RGBMatches(colorMap.GetRGB(style.FillForegroundColor), red, green, blue)) + return style.FillForegroundColor; + if (RGBMatches(colorMap.GetRGB(style.FillForegroundColor), red, green, blue)) + return style.FillForegroundColor; + // ToDo Other colours in style. + } + } + return null; + } + + private bool RGBMatches(byte[] rgb, byte red, byte green, byte blue) + { + if (rgb.Count() != 3) return false; + if (rgb[0] != red) return false; + if (rgb[1] != green) return false; + if (rgb[2] != blue) return false; + return true; + } + + //------------------------------------------------------------------- + private void ApplyStylingProperties(ICellStyle cellStyle, Dictionary styleProperties) + { + foreach (String propertyName in styleProperties.Keys) + { + object propertyValue = styleProperties[propertyName]; + if (propertyValue == null) + { + SaLogger.Info(SysUtil.Fn + " Null value for style property '" + propertyName + "'"); + continue; + } + switch (propertyName) + { + // Data format. + case CellUtil.DATA_FORMAT: + if (propertyValue is short) + { + cellStyle.DataFormat = (short)propertyValue; + continue; + } + break; + // Horizontal alignment. + case CellUtil.ALIGNMENT: + if (propertyValue is HorizontalAlignment || propertyValue is short) + { + cellStyle.Alignment = (HorizontalAlignment)propertyValue; + continue; + } + break; + // Vertical alignment. + case CellUtil.VERTICAL_ALIGNMENT: + if (propertyValue is VerticalAlignment || propertyValue is short) + { + cellStyle.VerticalAlignment = (VerticalAlignment)propertyValue; + continue; + } + break; + // Indentation. + case CellUtil.INDENTION: + if (propertyValue is short) + { + cellStyle.Indention = (short)propertyValue; + continue; + } + break; + // Wrap text. + case CellUtil.WRAP_TEXT: + if (propertyValue is bool) + { + cellStyle.WrapText = (bool)propertyValue; + continue; + } + break; + // Fill colours and pattern. + case CellUtil.FILL_FOREGROUND_COLOR: + if (propertyValue is short) + { + cellStyle.FillForegroundColor = (short)propertyValue; + continue; + } + break; + case CellUtil.FILL_BACKGROUND_COLOR: + if (propertyValue is short) + { + cellStyle.FillBackgroundColor = (short)propertyValue; + continue; + } + break; + case CellUtil.FILL_PATTERN: + if (propertyValue is FillPattern || propertyValue is short) + { + cellStyle.FillPattern = (FillPattern)propertyValue; + continue; + } + break; + // Hidden. + case CellUtil.HIDDEN: + if (propertyValue is bool) + { + cellStyle.IsHidden = (bool)propertyValue; + continue; + } + break; + // Locked. + case CellUtil.LOCKED: + if (propertyValue is bool) + { + cellStyle.IsLocked = (bool)propertyValue; + continue; + } + break; + // Rotation. + case CellUtil.ROTATION: + if (propertyValue is short) + { + cellStyle.Rotation = (short)propertyValue; + continue; + } + break; + // Shrink to fit. + case CellUtil.SHRINK_TO_FIT: + if (propertyValue is bool) + { + cellStyle.ShrinkToFit = (bool)propertyValue; + continue; + } + break; + // Borders. + case CellUtil.BORDER_TOP: + if (propertyValue is BorderStyle || propertyValue is short) + { + cellStyle.BorderTop = (BorderStyle)propertyValue; + continue; + } + break; + case CellUtil.TOP_BORDER_COLOR: + if (propertyValue is short) + { + cellStyle.TopBorderColor = (short)propertyValue; + continue; + } + break; + case CellUtil.BORDER_BOTTOM: + if (propertyValue is BorderStyle || propertyValue is short) + { + cellStyle.BorderBottom = (BorderStyle)propertyValue; + continue; + } + break; + case CellUtil.BOTTOM_BORDER_COLOR: + if (propertyValue is short) + { + cellStyle.BottomBorderColor = (short)propertyValue; + continue; + } + break; + case CellUtil.BORDER_LEFT: + if (propertyValue is BorderStyle || propertyValue is short) + { + cellStyle.BorderLeft = (BorderStyle)propertyValue; + continue; + } + break; + case CellUtil.LEFT_BORDER_COLOR: + if (propertyValue is short) + { + cellStyle.LeftBorderColor = (short)propertyValue; + continue; + } + break; + case CellUtil.BORDER_RIGHT: + if (propertyValue is BorderStyle || propertyValue is short) + { + cellStyle.BorderRight = (BorderStyle)propertyValue; + continue; + } + break; + case CellUtil.RIGHT_BORDER_COLOR: + if (propertyValue is short) + { + cellStyle.RightBorderColor = (short)propertyValue; + continue; + } + break; + case CellUtil.BORDER_DIAGONAL: + if (propertyValue is BorderDiagonal || propertyValue is short) + { + cellStyle.BorderDiagonal = (BorderDiagonal)propertyValue; + continue; + } + break; + // Font. + case CellUtil.FONT: + if (propertyValue is short) + { + IFont font = workbook.GetFontAt((short)propertyValue); + if (font != null) + cellStyle.SetFont(font); + continue; + } + if (propertyValue is IFont) + { + // Shouldn't get here. + cellStyle.SetFont((IFont)propertyValue); + continue; + } + break; + default: + SaLogger.Info(SysUtil.Fn + " Unrecognised style property '" + propertyName + "'"); + continue; + } + // Only get here for a valid property with invalid value type. + SaLogger.Info(SysUtil.Fn + " Invalid value type for style property '" + propertyName + "': " + propertyValue.GetType().Name); + } + } + + //------------------------------------------------------------------- + private BorderStyle GetBorderStyle(SaMsExcelBorderAttr borderAttr) + { + // POI Border style: + // None No border + // Thin Thin border + // Medium Medium border + // Dashed dash border + // Dotted dot border + // Thick Thick border + // Double double-line border + // Hair hair-line border + // MediumDashed Medium dashed border + // DashDot dash-dot border + // MediumDashDot medium dash-dot border + // DashDotDot dash-dot-dot border + // MediumDashDotDot medium dash-dot-dot border + // SlantedDashDot slanted dash-dot border + BorderStyle borderStyle = BorderStyle.None; + if (borderAttr.Weight != SaXlBorderWeight.NotSpecified) + { + if (borderAttr.Weight == SaXlBorderWeight.Hairline) + borderStyle = BorderStyle.Hair; + else if (borderAttr.Weight == SaXlBorderWeight.Thin) + borderStyle = BorderStyle.Thin; + else if (borderAttr.Weight == SaXlBorderWeight.Medium) + borderStyle = BorderStyle.Medium; + else if (borderAttr.Weight == SaXlBorderWeight.Thick) + borderStyle = BorderStyle.Thick; + } + if (borderAttr.LineStyle != SaXlLineStyle.NotSpecified) + { + if (borderAttr.LineStyle == SaXlLineStyle.None) + borderStyle = BorderStyle.None; + else if (borderAttr.LineStyle == SaXlLineStyle.Double) + borderStyle = BorderStyle.Double; + else if (borderAttr.LineStyle == SaXlLineStyle.Dot) + borderStyle = BorderStyle.Dotted; + else if (borderAttr.LineStyle == SaXlLineStyle.Dash) + borderStyle = borderAttr.Weight == SaXlBorderWeight.Medium || borderAttr.Weight == SaXlBorderWeight.Thick ? BorderStyle.MediumDashed : BorderStyle.Dashed; + else if (borderAttr.LineStyle == SaXlLineStyle.Continuous) + borderStyle = borderAttr.Weight == SaXlBorderWeight.Hairline ? BorderStyle.Hair : borderAttr.Weight == SaXlBorderWeight.Thin ? BorderStyle.Thin : borderAttr.Weight == SaXlBorderWeight.Medium ? BorderStyle.Medium : borderAttr.Weight == SaXlBorderWeight.Thick ? BorderStyle.Thick : BorderStyle.Thin; + else if (borderAttr.LineStyle == SaXlLineStyle.DashDot) + borderStyle = borderAttr.Weight == SaXlBorderWeight.Medium || borderAttr.Weight == SaXlBorderWeight.Thick ? BorderStyle.MediumDashDot : BorderStyle.DashDot; + else if (borderAttr.LineStyle == SaXlLineStyle.DashDotDot) + borderStyle = borderAttr.Weight == SaXlBorderWeight.Medium || borderAttr.Weight == SaXlBorderWeight.Thick ? BorderStyle.MediumDashDotDot : BorderStyle.DashDotDot; + else if (borderAttr.LineStyle == SaXlLineStyle.SlantDashDot) + borderStyle = BorderStyle.SlantedDashDot; + } + return borderStyle; + } + + private void SetCellStyle(uint colPos, uint rowPos, ICellStyle cellStyle) + { + ICell cell = GetCell(colPos, rowPos); + SetCellStyle(cell, cellStyle); + } + + private void SetCellStyle(ICell cell, ICellStyle cellStyle) + { + if (cell != null) + cell.CellStyle = cellStyle; } //------------------------------------------------------------------- // Add image to sheet. - public override bool InsertImage (SaMsExcelImage_Config imageConfig) + public override bool InsertImage(SaMsExcelImage_Config imageConfig) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - public override bool FreezePanes (uint colPos, uint rowPos) + public override bool FreezePanes(uint colPos, uint rowPos) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- - public override bool AutoFit (SaMsExcelRange range) + public override bool AutoFitRows(SaMsExcelRange range) { - return SetErrorState("Not supported"); + try + { + for (uint rowPos = range.RowFromPos; rowPos > 0 && rowPos <= range.RowToPos; rowPos++) + { + IRow row = worksheet.GetRow((int)rowPos - 1); + if (row != null) + row.Height = -1; + } + return true; + } + catch (Exception exc) { SetErrorState(SysUtil.Fn + " " + ExceptionUtil.GetErrorStateMessage(exc)); } + return false; } //------------------------------------------------------------------- - public override bool SelectRange (SaMsExcelRange range) + public override bool SelectRange(SaMsExcelRange range) { - return SetErrorState("Not supported"); + return SetErrorState(SysUtil.Fn + " " + Msg_NotSupported); } //------------------------------------------------------------------- diff --git a/SacsStandardProc/Proc/Procs/SaProcess.cs b/SacsStandardProc/Proc/Procs/SaProcess.cs index 34ed685..1589d89 100644 --- a/SacsStandardProc/Proc/Procs/SaProcess.cs +++ b/SacsStandardProc/Proc/Procs/SaProcess.cs @@ -43,22 +43,6 @@ namespace Sarand.Net.Standard.Proc.Procs // Utilities. //------------------------------------------------------------------- - //------------------------------------------------------------------- - // Run asynchronous. - //------------------------------------------------------------------- - public bool RunAsync() - { - // Run on separate thread. - Thread thread = new Thread(new ThreadStart(RunOnThread)); - thread.IsBackground = true; - thread.Start(); - return true; - } - private void RunOnThread() - { - RunProcess(); - } - //------------------------------------------------------------------- } //======================================================================= diff --git a/SacsStandardProc/SacsStandardProc.csproj b/SacsStandardProc/SacsStandardProc.csproj index b026d94..e52ba04 100644 --- a/SacsStandardProc/SacsStandardProc.csproj +++ b/SacsStandardProc/SacsStandardProc.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 Sarand.Net.Standard @@ -11,7 +11,7 @@ - +