From 72fe158ab2707ec8fdea86534c95e23a2ad85eef Mon Sep 17 00:00:00 2001 From: FastReports-bot Date: Wed, 16 Oct 2024 16:13:30 +0300 Subject: [PATCH] * sync 10/16/2024 version: 2025.1.0 --- .../FastReport.Data/Directory.Build.targets | 8 +- .../FastReport.Data.Cassandra/Shared.props | 1 + .../ClickHouseDataConnection.cs | 137 ++++- .../FastReport.Data.ClickHouse/Shared.props | 2 +- .../FastReport.Data.Excel/Shared.props | 1 + .../FastReport.Data.MsSql/Shared.props | 2 +- .../MySqlDataConnection.cs | 47 +- .../OracleDataConnection.cs | 178 +++++- .../Shared.props | 2 +- .../PostgresDataConnection.cs | 411 ++++++++++++- .../PostgresTypesParsers.cs | 117 ++++ .../FastReport.Data.Postgres/Shared.props | 2 +- .../FastReport.Data.RavenDB/Shared.props | 3 + .../FastReport.Plugin/Directory.Build.targets | 4 +- FastReport.Base/BandBase.Async.cs | 28 + FastReport.Base/BandBase.cs | 3 - .../Barcode/BarcodeObject.Async.cs | 19 + FastReport.Base/Barcode/BarcodeObject.cs | 5 + FastReport.Base/Base.cs | 22 + FastReport.Base/CheckBoxObject.Async.cs | 20 + FastReport.Base/CheckBoxObject.cs | 5 + .../Code/AssemblyDescriptor.Async.cs | 207 +++++++ FastReport.Base/Code/AssemblyDescriptor.cs | 195 +------ FastReport.Base/ContainerObject.Async.cs | 32 ++ .../CrossView/CrossViewObject.Async.cs | 24 + FastReport.Base/CrossView/CrossViewObject.cs | 6 +- FastReport.Base/Data/DataConnectionBase.cs | 4 +- FastReport.Base/Data/Dictionary.cs | 44 +- FastReport.Base/DataBand.cs | 10 +- FastReport.Base/Engine/ReportEngine.Async.cs | 72 +++ .../Engine/ReportEngine.Bands.Async.cs | 316 ++++++++++ FastReport.Base/Engine/ReportEngine.Bands.cs | 11 +- .../Engine/ReportEngine.DataBands.Async.cs | 291 ++++++++++ .../Engine/ReportEngine.Groups.Async.cs | 162 ++++++ FastReport.Base/Engine/ReportEngine.Groups.cs | 8 +- .../Engine/ReportEngine.Pages.Async.cs | 133 +++++ FastReport.Base/Engine/ReportEngine.Pages.cs | 79 +-- .../Engine/ReportEngine.Subreports.Async.cs | 81 +++ FastReport.Base/Export/ExportUtils.cs | 14 +- FastReport.Base/Export/Html/HTMLExportDraw.cs | 7 +- .../Export/Html/HTMLExportLayers.cs | 2 +- FastReport.Base/Functions/NumToWordsBase.cs | 30 +- FastReport.Base/Functions/StdFunctions.cs | 538 +++++++++++++++++- FastReport.Base/Gauge/GaugeObject.Async.cs | 20 + FastReport.Base/Gauge/GaugeObject.cs | 4 + FastReport.Base/HtmlObject.Async.cs | 20 + FastReport.Base/HtmlObject.cs | 4 + FastReport.Base/IContainDataSource.cs | 9 + FastReport.Base/Matrix/MatrixObject.Async.cs | 20 + FastReport.Base/Matrix/MatrixObject.cs | 14 +- FastReport.Base/PictureObject.Async.cs | 73 +++ FastReport.Base/PictureObject.cs | 14 +- FastReport.Base/PictureObjectBase.Async.cs | 44 ++ FastReport.Base/PictureObjectBase.cs | 24 +- FastReport.Base/RFIDLabel.Async.cs | 17 + FastReport.Base/RFIDLabel.cs | 5 + FastReport.Base/Report.Async.cs | 117 ++++ FastReport.Base/Report.cs | 195 ++++--- FastReport.Base/ReportComponentBase.Async.cs | 14 + FastReport.Base/ReportComponentBase.cs | 5 + FastReport.Base/ReportPage.cs | 240 ++++++++ FastReport.Base/Table/TableCell.Async.cs | 20 + FastReport.Base/Table/TableCell.cs | 5 + FastReport.Base/Table/TableObject.Async.cs | 20 + FastReport.Base/Table/TableObject.cs | 5 + FastReport.Base/TextObject.Async.cs | 21 + FastReport.Base/TextObject.cs | 5 + FastReport.Base/Utils/FRReader.cs | 2 +- FastReport.Base/Utils/FRWriter.cs | 10 + FastReport.Base/Utils/FontManager.Gdi.cs | 34 +- FastReport.Base/Utils/HtmlTextRenderer.cs | 50 ++ FastReport.Base/Utils/ImageHelper.Async.cs | 42 ++ FastReport.Base/Utils/ImageHelper.cs | 2 +- FastReport.Base/Utils/TextRenderer.cs | 2 +- FastReport.Base/ZipCodeObject.Async.cs | 20 + FastReport.Base/ZipCodeObject.cs | 5 + FastReport.Compat/Directory.Build.props | 5 +- .../Application/Cache/IWebReportCache.cs | 6 +- .../Application/DesignerSettings.cs | 2 +- .../Infrastructure/ControllerBuilder.cs | 2 +- .../Application/LinkerFlags.cs | 93 --- .../Application/WebReport.Backend.cs | 18 + .../Application/WebReport.Tabs.cs | 20 +- FastReport.Core.Web/Application/WebReport.cs | 31 +- FastReport.Core.Web/Application/WebUtils.cs | 7 +- .../Designer/ConnectionsController.cs | 55 +- .../Preview/GetReportController.cs | 8 +- FastReport.Core.Web/Directory.Build.targets | 4 +- .../Services/Abstract/IConnectionsService.cs | 27 +- .../Services/Abstract/IReportService.cs | 8 - .../Implementation/ConnectionService.cs | 250 +++++--- .../Implementation/InternalResourceLoader.cs | 3 +- .../Services/Implementation/ReportService.cs | 15 +- .../Services/ServicesParamsModels.cs | 16 + .../Engine/ReportEngine.Dialogs.OpenSource.cs | 6 + FastReport/Resources/en.xml | 19 +- Localization/Arabic.frl | 12 +- Localization/Armenian.frl | 12 +- Localization/Croatian.frl | 8 +- Localization/Czech.frl | 12 +- Localization/Danish.frl | 8 +- Localization/Dutch.frl | 8 +- Localization/French.frl | 12 +- Localization/German.frl | 12 +- Localization/Greek.frl | 12 +- Localization/Hungarian.frl | 12 +- Localization/Persian.frl | 8 +- Localization/Romanian.frl | 12 +- Localization/Russian.frl | 36 +- Localization/Slovak.frl | 12 +- Localization/Slovenian.frl | 12 +- Localization/Spanish.frl | 12 +- Localization/Thai.frl | 8 +- Localization/Ukrainian.frl | 8 +- Pack/BuildScripts/Tasks/BaseTasks.cs | 3 + .../BuildScripts/Tasks/LocalizationPackage.cs | 10 +- UsedPackages.version | 8 +- 117 files changed, 4418 insertions(+), 779 deletions(-) create mode 100644 Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresTypesParsers.cs create mode 100644 FastReport.Base/BandBase.Async.cs create mode 100644 FastReport.Base/Barcode/BarcodeObject.Async.cs create mode 100644 FastReport.Base/CheckBoxObject.Async.cs create mode 100644 FastReport.Base/Code/AssemblyDescriptor.Async.cs create mode 100644 FastReport.Base/ContainerObject.Async.cs create mode 100644 FastReport.Base/CrossView/CrossViewObject.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.Bands.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.DataBands.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.Groups.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.Pages.Async.cs create mode 100644 FastReport.Base/Engine/ReportEngine.Subreports.Async.cs create mode 100644 FastReport.Base/Gauge/GaugeObject.Async.cs create mode 100644 FastReport.Base/HtmlObject.Async.cs create mode 100644 FastReport.Base/IContainDataSource.cs create mode 100644 FastReport.Base/Matrix/MatrixObject.Async.cs create mode 100644 FastReport.Base/PictureObject.Async.cs create mode 100644 FastReport.Base/PictureObjectBase.Async.cs create mode 100644 FastReport.Base/RFIDLabel.Async.cs create mode 100644 FastReport.Base/Report.Async.cs create mode 100644 FastReport.Base/ReportComponentBase.Async.cs create mode 100644 FastReport.Base/Table/TableCell.Async.cs create mode 100644 FastReport.Base/Table/TableObject.Async.cs create mode 100644 FastReport.Base/TextObject.Async.cs create mode 100644 FastReport.Base/Utils/ImageHelper.Async.cs create mode 100644 FastReport.Base/ZipCodeObject.Async.cs diff --git a/Extras/Core/FastReport.Data/Directory.Build.targets b/Extras/Core/FastReport.Data/Directory.Build.targets index e76b4f8b..0c9b2eb8 100644 --- a/Extras/Core/FastReport.Data/Directory.Build.targets +++ b/Extras/Core/FastReport.Data/Directory.Build.targets @@ -2,10 +2,10 @@ - - - - + + + + \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Cassandra/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.Cassandra/Shared.props index 503b2404..953e4450 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Cassandra/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.Cassandra/Shared.props @@ -9,6 +9,7 @@ + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/ClickHouseDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/ClickHouseDataConnection.cs index 687b40e4..69fa5b85 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/ClickHouseDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/ClickHouseDataConnection.cs @@ -10,6 +10,7 @@ using System.Data; using System.Data.Common; using System.Linq; +using System.Net; using System.Text; using System.Threading.Tasks; @@ -83,6 +84,7 @@ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection conn clickHouseDataAdapter.SelectCommand = command; return clickHouseDataAdapter; } + private string PrepareSelectCommand(string selectCommand, string tableName, DbConnection connection) { if (String.IsNullOrEmpty(selectCommand)) @@ -91,6 +93,7 @@ private string PrepareSelectCommand(string selectCommand, string tableName, DbCo } return selectCommand; } + private IEnumerable GetColumns(ClickHouseDataReader reader) { for (int i = 0; i < reader.FieldCount; i++) @@ -112,8 +115,8 @@ public override void FillTableSchema(DataTable table, string selectCommand, Comm selectCommand = PrepareSelectCommand(selectCommand, table.TableName, clickHouseConnection); /*To reduce size of traffic and size of answer from ClickHouse server. Because FillSchema doesn't work in this ADO.NET library. - LIMIT 0 gets an empy set, but we still have list of desired columns - Prorably can be a better way. + LIMIT 0 gets an empty set, but we still have list of desired columns + Probably can be a better way. */ selectCommand += " LIMIT 0"; ClickHouseCommand clickHouseCommand = clickHouseConnection.CreateCommand(); @@ -121,7 +124,15 @@ Prorably can be a better way. foreach (CommandParameter p in parameters) { selectCommand = selectCommand.Replace($"@{p.Name}", $"{{{p.Name}:{(ClickHouseTypeCode)p.DataType}}}"); - clickHouseCommand.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), p.Value); + if (p.Value is Variant value) + { + if (value.Type == typeof(string)) + clickHouseCommand.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), VariantToClrType(value, (ClickHouseTypeCode)p.DataType)); + else + clickHouseCommand.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), value.ToType(value.Type)); + } + else + clickHouseCommand.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), p.Value); } clickHouseCommand.CommandText = selectCommand; using (ClickHouseDataReader reader = clickHouseCommand.ExecuteReader() as ClickHouseDataReader) @@ -135,5 +146,125 @@ Prorably can be a better way. DisposeConnection(clickHouseConnection); } } + + private object VariantToClrType(Variant value, ClickHouseTypeCode type) + { + if (value.ToString() == "" && type != ClickHouseTypeCode.Nothing) + return null; + + switch (type) + { + case ClickHouseTypeCode.Enum8: + case ClickHouseTypeCode.Int8: + { + sbyte val = 0; + sbyte.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Enum16: + case ClickHouseTypeCode.Int16: + { + short val = 0; + short.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Int32: + { + int val = 0; + int.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Int64: + { + long val = 0; + long.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.UInt8: + { + byte val = 0; + byte.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.UInt16: + { + ushort val = 0; + ushort.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.UInt32: + { + uint val = 0; + uint.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.UInt64: + { + ulong val = 0; + ulong.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Date: + { + DateTime val = DateTime.Now; + DateTime.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.DateTime: + case ClickHouseTypeCode.DateTime64: + { + DateTimeOffset val = DateTimeOffset.Now; + DateTimeOffset.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Decimal: + { + decimal val = 0; + decimal.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Float32: + { + float val = 0; + float.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.Float64: + { + double val = 0; + double.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.UUID: + { + Guid val = Guid.Empty; + Guid.TryParse(value.ToString(), out val); + return val; + } + case ClickHouseTypeCode.IPv6: + case ClickHouseTypeCode.IPv4: + { + try + { + return IPAddress.Parse(value.ToString()); + } + catch + { + return IPAddress.None; + } + } + case ClickHouseTypeCode.Nothing: + return DBNull.Value; + case ClickHouseTypeCode.Array: + case ClickHouseTypeCode.Nested: + case ClickHouseTypeCode.Tuple: + case ClickHouseTypeCode.Nullable: + case ClickHouseTypeCode.LowCardinality: + throw new NotImplementedException(); + + default: + return value.ToString(); + } + } } } diff --git a/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/Shared.props index 531189a1..143262ee 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.ClickHouse/Shared.props @@ -12,7 +12,7 @@ - + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Excel/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.Excel/Shared.props index 36f2a6f9..62238dbd 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Excel/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.Excel/Shared.props @@ -10,6 +10,7 @@ + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props index bf62f837..3c3523d6 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props @@ -8,7 +8,7 @@ - + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlDataConnection.cs index 8b2e3016..181cfe4d 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlDataConnection.cs @@ -3,6 +3,7 @@ using System.Data.Common; using MySqlConnector; using System.Data; +using System.Globalization; namespace FastReport.Data { @@ -64,7 +65,7 @@ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection conn { MySqlDataAdapter adapter = new MySqlDataAdapter(selectCommand, connection as MySqlConnection); foreach (CommandParameter p in parameters) - { + { MySqlParameter parameter = adapter.SelectCommand.Parameters.Add(p.Name, (MySqlDbType)p.DataType, p.Size); if (p.Value is Variant value) @@ -83,7 +84,7 @@ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection conn private object VariantToClrType(Variant value, MySqlDbType type) { - if (value.ToString() == "") + if (value.ToString() == "" && type != MySqlDbType.Null) return null; switch (type) @@ -116,19 +117,16 @@ private object VariantToClrType(Variant value, MySqlDbType type) return val; } case MySqlDbType.DateTime: + case MySqlDbType.Timestamp: case MySqlDbType.Date: + case MySqlDbType.Newdate: + case MySqlDbType.Time: { DateTime val = DateTime.Now; DateTime.TryParse(value.ToString(), out val); return val; } - case MySqlDbType.Time: - case MySqlDbType.Timestamp: - { - TimeSpan val = TimeSpan.Zero; - TimeSpan.TryParse(value.ToString(), out val); - return val; - } + case MySqlDbType.NewDecimal: case MySqlDbType.Decimal: { decimal val = 0; @@ -142,13 +140,13 @@ private object VariantToClrType(Variant value, MySqlDbType type) return val; } case MySqlDbType.Int24: - case MySqlDbType.UInt24: case MySqlDbType.Int32: { int val = 0; int.TryParse(value.ToString(), out val); return val; } + case MySqlDbType.UInt24: case MySqlDbType.UInt32: { uint val = 0; @@ -174,12 +172,41 @@ private object VariantToClrType(Variant value, MySqlDbType type) return val; } case MySqlDbType.Byte: + { + sbyte val = 0; + sbyte.TryParse(value.ToString(), out val); + return val; + } case MySqlDbType.UByte: { byte val = 0; byte.TryParse(value.ToString(), out val); return val; } + case MySqlDbType.Blob: + case MySqlDbType.TinyBlob: + case MySqlDbType.MediumBlob: + case MySqlDbType.LongBlob: + case MySqlDbType.Binary: + case MySqlDbType.VarBinary: + { + string val = value.ToString(); + if (val.Length % 2 != 0) + { + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", val)); + } + + byte[] data = new byte[val.Length / 2]; + for (int index = 0; index < data.Length; index++) + { + string byteValue = val.Substring(index * 2, 2); + data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return data; + } + case MySqlDbType.Null: + return DBNull.Value; default: return value.ToString(); } diff --git a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleDataConnection.cs index 2fa974df..1b6a6041 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleDataConnection.cs @@ -9,7 +9,7 @@ namespace FastReport.Data { public class OracleDataConnection : DataConnectionBase { - private void GetDBObjectNames(string name, string columnName, List list) + private void GetDBObjectNames(string name, string columnName, List list, bool ignoreShema = false) { DataTable schema = null; DbConnection connection = GetConnection(); @@ -28,7 +28,7 @@ private void GetDBObjectNames(string name, string columnName, List list) { string tableName = row[columnName].ToString(); string schemaName = row["OWNER"].ToString(); - if (String.Compare(schemaName, "SYSTEM") == 0) + if (String.Compare(schemaName, "SYSTEM") == 0 || ignoreShema) list.Add(tableName); else list.Add(schemaName + ".\"" + tableName + "\""); @@ -57,7 +57,7 @@ protected override string GetConnectionStringWithLoginInfo(string userName, stri builder.UserID = userName; builder.Password = password; - + return builder.ToString(); } @@ -77,7 +77,6 @@ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection conn OracleDbType parType = (OracleDbType)p.DataType; OracleParameter parameter = adapter.SelectCommand.Parameters.Add(p.Name, parType, p.Size); parameter.Value = p.Value; - // if we have refcursor parameter, set its direction to output, and also // modify the command type to CommandType.StoredProcedure. The selectCommand must contain // the stored proc name only. @@ -96,6 +95,177 @@ public override Type GetParameterType() return typeof(OracleDbType); } + public override string[] GetProcedureNames() + { + List list = new List(); + GetDBObjectNames("Procedures", "OBJECT_NAME", list, true); + return list.ToArray(); + } + + private DataTable GetSchema(string selectCommand) + { + var connection = GetConnection(); + try + { + OpenConnection(connection); + + var dataset = new DataSet(); + var adapter = new OracleDataAdapter(selectCommand, connection as OracleConnection); + adapter.Fill(dataset); + + if (dataset.Tables.Count > 0) + return dataset.Tables[0]; + } + finally + { + DisposeConnection(connection); + } + + return null; + } + + public override TableDataSource CreateProcedure(string tableName) + { + ProcedureDataSource table = new ProcedureDataSource(); + table.Enabled = true; + table.SelectCommand = tableName; + DbConnection conn = GetConnection(); + try + { + OpenConnection(conn); + var schemaParameters = GetSchema($"SELECT * FROM SYS.ALL_ARGUMENTS WHERE OBJECT_NAME='{tableName}'"); + foreach (DataRow row in schemaParameters.Rows) + { + ParameterDirection direction = ParameterDirection.Input; + switch (row["IN_OUT"].ToString()) + { + case "IN": + direction = ParameterDirection.Input; + table.Enabled = false; + break; + case "IN/OUT": + direction = ParameterDirection.InputOutput; + table.Enabled = false; + break; + case "OUT": + direction = ParameterDirection.Output; + break; + } +; + if (!int.TryParse(row["DATA_PRECISION"].ToString(), out int precision)) + precision = 0; + if (!int.TryParse(row["DATA_SCALE"].ToString(), out int scale)) + scale = 0; + table.Parameters.Add(new ProcedureParameter() + { + Name = row["ARGUMENT_NAME"].ToString(), + DataType = (int)MapTypes(row["DATA_TYPE"].ToString(), Convert.ToInt32(precision), Convert.ToInt32(scale)), + Direction = direction, + + }); + } + } + finally + { + DisposeConnection(conn); + } + + return table; + } + + public OracleDbType MapTypes(string dataType, int precision, int scale) + { + switch (dataType) + { + case "BFILE": + return OracleDbType.BFile; + case "BINARY_FLOAT": + return OracleDbType.BinaryFloat; + case "BINARY_DOUBLE": + return OracleDbType.BinaryDouble; + case "BLOB": + return OracleDbType.Blob; + case "BOOLEAN": + case "PL/SQL BOOLEAN": + return OracleDbType.Boolean; + case "byte": + return OracleDbType.Byte; + case "CHAR": + return OracleDbType.Char; + case "CLOB": + return OracleDbType.Clob; + case "DATE": + return OracleDbType.Date; + case "NUMBER": + case "INTEGER": + case "FLOAT": + return ConvertNumberToOraDbType(precision, scale); + case "INTERVAL DAY TO SECOND": + return OracleDbType.IntervalDS; + case "INTERVAL YEAR TO MONTH": + return OracleDbType.IntervalYM; + case "LONG": + return OracleDbType.Long; + case "LONG RAW": + return OracleDbType.BFile; + case "NCHAR": + return OracleDbType.NChar; + case "NCLOB": + return OracleDbType.NClob; + case "NVARCHAR2": + return OracleDbType.NVarchar2; + case "RAW": + return OracleDbType.Raw; + case "REF CURSOR": + return OracleDbType.RefCursor; + case "TIMESTAMP": + return OracleDbType.TimeStamp; + case "TIMESTAMP WITH LOCAL TIME ZONE": + return OracleDbType.TimeStampLTZ; + case "TIMESTAMP WITH TIME ZONE": + return OracleDbType.TimeStampTZ; + case "VARCHAR2": + return OracleDbType.Varchar2; + case "OPAQUE/XMLTYPE": + case "XMLTYPE": + return OracleDbType.XmlType; + } + + return OracleDbType.Varchar2; + } + + private static OracleDbType ConvertNumberToOraDbType(int precision, int scale) + { + OracleDbType result = OracleDbType.Decimal; + if (scale <= 0 && precision - scale < 5) + { + result = OracleDbType.Int16; + } + else if (scale <= 0 && precision - scale < 10) + { + result = OracleDbType.Int32; + } + else if (scale <= 0 && precision - scale < 19) + { + result = OracleDbType.Int64; + } + else if (precision < 8 && ((scale <= 0 && precision - scale <= 38) || (scale > 0 && scale <= 44))) + { + result = OracleDbType.Single; + } + else if (precision < 16) + { + result = OracleDbType.Double; + } + + return result; + } + + public OracleDataConnection() + { + CanContainProcedures = true; + } + #if !FRCORE public override int GetDefaultParameterType() { diff --git a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/Shared.props index 3bc453cd..0397c100 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/Shared.props @@ -8,7 +8,7 @@ - + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresDataConnection.cs index ff351a04..4cf9b961 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresDataConnection.cs @@ -1,9 +1,12 @@ using Npgsql; using NpgsqlTypes; using System; +using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Globalization; +using System.Net.NetworkInformation; namespace FastReport.Data { @@ -59,6 +62,133 @@ private DataTable GetSchema(string selectCommand) return null; } + private int MapTypes(string data_type) + { + var parenIndex = data_type.IndexOf('('); + if (parenIndex > -1) + data_type = data_type.Substring(0, parenIndex); + + NpgsqlDbType result = NpgsqlDbType.Unknown; + + switch (data_type) + { + // Numeric types + case "smallint": result = NpgsqlDbType.Smallint; break; + case "integer": + case "int": result = NpgsqlDbType.Integer; break; + case "bigint": result = NpgsqlDbType.Bigint; break; + case "real": result = NpgsqlDbType.Real; break; + case "double precision": result = NpgsqlDbType.Double; break; + case "numeric": result = NpgsqlDbType.Numeric; break; + case "money": result = NpgsqlDbType.Money; break; + + // Text types + case "text": result = NpgsqlDbType.Text; break; + case "xml": result = NpgsqlDbType.Xml; break; + case "character varying": + case "varchar": result = NpgsqlDbType.Varchar; break; + case "character": result = NpgsqlDbType.Char; break; + case "name": result = NpgsqlDbType.Name; break; + case "refcursor": result = NpgsqlDbType.Refcursor; break; + case "citext": result = NpgsqlDbType.Citext; break; + case "jsonb": result = NpgsqlDbType.Jsonb; break; + case "json": result = NpgsqlDbType.Json; break; + case "jsonpath": result = NpgsqlDbType.JsonPath; break; + + // Date/time types + case "timestamp without time zone": + case "timestamp": result = NpgsqlDbType.Timestamp; break; + case "timestamp with time zone": + case "timestamptz": result = NpgsqlDbType.TimestampTz; break; + case "date": result = NpgsqlDbType.Date; break; + case "time without time zone": + case "timetz": result = NpgsqlDbType.Time; break; + case "time with time zone": + case "time": result = NpgsqlDbType.TimeTz; break; + case "interval": result = NpgsqlDbType.Interval; break; + + // Network types + case "cidr": result = NpgsqlDbType.Cidr; break; + case "inet": result = NpgsqlDbType.Inet; break; + case "macaddr": result = NpgsqlDbType.MacAddr; break; + case "macaddr8": result = NpgsqlDbType.MacAddr8; break; + + // Full-text search types + case "tsquery": result = NpgsqlDbType.TsQuery; break; + case "tsvector": result = NpgsqlDbType.TsVector; break; + + // Geometry types + case "box": result = NpgsqlDbType.Box; break; + case "circle": result = NpgsqlDbType.Circle; break; + case "line": result = NpgsqlDbType.Line; break; + case "lseg": result = NpgsqlDbType.LSeg; break; + case "path": result = NpgsqlDbType.Path; break; + case "point": result = NpgsqlDbType.Point; break; + case "polygon": result = NpgsqlDbType.Polygon; break; + + // LTree types + case "lquery": result = NpgsqlDbType.LQuery; break; + case "ltree": result = NpgsqlDbType.LTree; break; + case "ltxtquery": result = NpgsqlDbType.LTxtQuery; break; + + // UInt types + case "oid": result = NpgsqlDbType.Oid; break; + case "xid": result = NpgsqlDbType.Xid; break; + case "xid8": result = NpgsqlDbType.Xid8; break; + case "cid": result = NpgsqlDbType.Cid; break; + case "regtype": result = NpgsqlDbType.Regtype; break; + case "regconfig": result = NpgsqlDbType.Regconfig; break; + + // Misc types + case "boolean": + case "bool": result = NpgsqlDbType.Boolean; break; + case "bytea": result = NpgsqlDbType.Bytea; break; + case "uuid": result = NpgsqlDbType.Uuid; break; + case "bit varying": + case "varbit": result = NpgsqlDbType.Varbit; break; + case "bit": result = NpgsqlDbType.Bit; break; + case "hstore": result = NpgsqlDbType.Hstore; break; + + case "geometry": result = NpgsqlDbType.Geometry; break; + case "geography": result = NpgsqlDbType.Geography; break; + + // Built-in range types + case "int4range": result = NpgsqlDbType.IntegerRange; break; + case "int8range": result = NpgsqlDbType.BigIntRange; break; + case "numrange": result = NpgsqlDbType.NumericRange; break; + case "tsrange": result = NpgsqlDbType.TimestampRange; break; + case "tstzrange": result = NpgsqlDbType.TimestampTzRange; break; + case "daterange": result = NpgsqlDbType.DateRange; break; + + // Built-in multirange types + case "int4multirange": result = NpgsqlDbType.IntegerMultirange; break; + case "int8multirange": result = NpgsqlDbType.BigIntMultirange; break; + case "nummultirange": result = NpgsqlDbType.NumericMultirange; break; + case "tsmultirange": result = NpgsqlDbType.TimestampMultirange; break; + case "tstzmultirange": result = NpgsqlDbType.TimestampTzMultirange; break; + case "datemultirange": result = NpgsqlDbType.DateMultirange; break; + + // Internal types + case "int2vector": result = NpgsqlDbType.Int2Vector; break; + case "oidvector": result = NpgsqlDbType.Oidvector; break; + case "pg_lsn": result = NpgsqlDbType.PgLsn; break; + case "tid": result = NpgsqlDbType.Tid; break; + case "char": result = NpgsqlDbType.InternalChar; break; + + default: + { + if (data_type.EndsWith("[]", StringComparison.Ordinal)) + { + NpgsqlDbType elementNpgsqlDbType = (NpgsqlDbType)MapTypes(data_type.Substring(0, data_type.Length - 2)); + result = elementNpgsqlDbType != NpgsqlDbType.Unknown ? elementNpgsqlDbType | NpgsqlDbType.Array : NpgsqlDbType.Unknown; // e.g. ranges + } + break; + } + } + + return (int)result; + } + /// public override string[] GetTableNames() { @@ -107,6 +237,7 @@ public override string[] GetProcedureNames() "WHERE routine_type = 'FUNCTION'"; var schema = GetSchema(selectCommand); + if (schema != null) { foreach (DataRow row in schema.Rows) @@ -183,13 +314,14 @@ public override TableDataSource CreateProcedure(string tableName) case "OUT": // skip completely: it's a result table's column + default: continue; } table.Parameters.Add(new ProcedureParameter() { Name = row["parameter_name"].ToString(), - DataType = (int)(NpgsqlDbType)Enum.Parse(typeof(NpgsqlDbType), row["data_type"].ToString(), true), + DataType = MapTypes(row["data_type"].ToString()), Direction = direction }); } @@ -235,14 +367,289 @@ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection conn foreach (CommandParameter p in parameters) { NpgsqlParameter parameter = adapter.SelectCommand.Parameters.Add(p.Name, (NpgsqlDbType)p.DataType, p.Size); - parameter.Value = p.Value; + if (p.Value is Variant value) + { + if (value.Type == typeof(string)) + parameter.Value = VariantToClrType(value, (NpgsqlDbType)p.DataType); + else + parameter.Value = value.ToType(value.Type); + } + else + parameter.Value = p.Value; } return adapter; } + private object VariantToClrType(Variant value, NpgsqlDbType type) + { + if (value.ToString() == "") + return null; + + switch (type) + { + case NpgsqlDbType.Bigint: + { + long val = 0; + long.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Boolean: + { + bool val = false; + bool.TryParse(value.ToString(), out val); + if (value.ToString() == "1") + val = true; + return val; + } + case NpgsqlDbType.Double: + { + double val = 0; + double.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Timestamp: + case NpgsqlDbType.TimestampTz: + case NpgsqlDbType.Date: + { + DateTime val = DateTime.Now; + DateTime.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Time: + case NpgsqlDbType.Interval: + { + TimeSpan val = TimeSpan.Zero; + TimeSpan.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.TimeTz: + { + DateTimeOffset val = DateTimeOffset.Now; + DateTimeOffset.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Money: + case NpgsqlDbType.Numeric: + { + decimal val = 0; + decimal.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Real: + { + float val = 0; + float.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Integer: + { + int val = 0; + int.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Oid: + case NpgsqlDbType.Xid: + case NpgsqlDbType.Cid: + { + uint val = 0; + uint.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Tid: + { + string[] values = value.ToString().Replace("(", "").Replace(")", "").Split(','); + + uint val1 = 0; + uint.TryParse(values[0], out val1); + + ushort val2 = 0; + ushort.TryParse(values[1], out val2); + + return new NpgsqlTid(val1, val2); + } + case NpgsqlDbType.Uuid: + { + Guid val = Guid.Empty; + Guid.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Smallint: + { + short val = 0; + short.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.InternalChar: + { + byte val = 0; + byte.TryParse(value.ToString(), out val); + return val; + } + case NpgsqlDbType.Box: + { + try + { + return PostgresTypesParsers.ParseBox(value.ToString()); + } + catch + { + return new NpgsqlBox(); + } + } + case NpgsqlDbType.Circle: + { + try + { + return PostgresTypesParsers.ParseCircle(value.ToString()); + } + catch + { + return new NpgsqlCircle(); + } + } + case NpgsqlDbType.Line: + { + try + { + return PostgresTypesParsers.ParseLine(value.ToString()); + } + catch + { + return new NpgsqlLine(); + } + } + case NpgsqlDbType.Polygon: + { + try + { + return PostgresTypesParsers.ParsePolygon(value.ToString()); + } + catch + { + return new NpgsqlPolygon(); + } + } + case NpgsqlDbType.Path: + { + try + { + return PostgresTypesParsers.ParsePath(value.ToString()); + } + catch + { + return new NpgsqlPath(); + } + } + case NpgsqlDbType.LSeg: + { + try + { + return PostgresTypesParsers.ParseLSeg(value.ToString()); + } + catch + { + return new NpgsqlLSeg(); + } + } + case NpgsqlDbType.Point: + { + try + { + return PostgresTypesParsers.ParsePoint(value.ToString()); + } + catch + { + return new NpgsqlPoint(); + } + } + case NpgsqlDbType.Cidr: + { + try + { + return new NpgsqlCidr(value.ToString()); + } + catch + { + return new NpgsqlCidr(); + } + } + case NpgsqlDbType.Inet: + { + try + { + return new NpgsqlInet(value.ToString()); + } + catch + { + return new NpgsqlInet(); + } + } + case NpgsqlDbType.MacAddr: + { + try + { + return PhysicalAddress.Parse(value.ToString()); + } + catch + { + return PhysicalAddress.None; + } + } + case NpgsqlDbType.TsQuery: + { + try + { + return NpgsqlTsQuery.Parse(value.ToString()); + } + catch + { + return null; + } + } + case NpgsqlDbType.TsVector: + { + try + { + return NpgsqlTsVector.Parse(value.ToString()); + } + catch + { + return null; + } + } + case NpgsqlDbType.Bytea: + { + string val = value.ToString(); + if (val.Length % 2 != 0) + { + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", val)); + } + + byte[] data = new byte[val.Length / 2]; + for (int index = 0; index < data.Length; index++) + { + string byteValue = val.Substring(index * 2, 2); + data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); + } + + return data; + } + case NpgsqlDbType.Array: + case NpgsqlDbType.Range: + case NpgsqlDbType.Hstore: + case NpgsqlDbType.Oidvector: + case NpgsqlDbType.MacAddr8: + case NpgsqlDbType.Int2Vector: + throw new NotImplementedException(); + default: + return value.ToString(); + } + } + public PostgresDataConnection() { CanContainProcedures = true; + AppContext.SetSwitch("Npgsql.EnableStoredProcedureCompatMode", true); } } } diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresTypesParsers.cs b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresTypesParsers.cs new file mode 100644 index 00000000..248bf182 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresTypesParsers.cs @@ -0,0 +1,117 @@ +using NpgsqlTypes; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Text.RegularExpressions; + + +namespace FastReport.Data +{ + internal static class PostgresTypesParsers + { + static readonly Regex pointRegex = new Regex(@"\((-?\d+.?\d*),(-?\d+.?\d*)\)", RegexOptions.Compiled); + static readonly Regex lineRegex = new Regex(@"\{(-?\d+.?\d*),(-?\d+.?\d*),(-?\d+.?\d*)\}", RegexOptions.Compiled); + static readonly Regex lSegRegex = new Regex(@"\[\((-?\d+.?\d*),(-?\d+.?\d*)\),\((-?\d+.?\d*),(-?\d+.?\d*)\)\]", RegexOptions.Compiled); + static readonly Regex boxRegex = new Regex(@"\((-?\d+.?\d*),(-?\d+.?\d*)\),\((-?\d+.?\d*),(-?\d+.?\d*)\)", RegexOptions.Compiled); + static readonly Regex circleRegex = new Regex(@"<\((-?\d+.?\d*),(-?\d+.?\d*)\),(\d+.?\d*)>", RegexOptions.Compiled); + + public static NpgsqlPoint ParsePoint(string s) + { + var m = pointRegex.Match(s); + if (!m.Success) + { + throw new FormatException("Not a valid point: " + s); + } + return new NpgsqlPoint(double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)); + } + + public static NpgsqlLine ParseLine(string s) + { + var m = lineRegex.Match(s); + if (!m.Success) + throw new FormatException("Not a valid line: " + s); + return new NpgsqlLine( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + } + + public static NpgsqlLSeg ParseLSeg(string s) + { + var m = lSegRegex.Match(s); + if (!m.Success) + { + throw new FormatException("Not a valid line: " + s); + } + return new NpgsqlLSeg( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[4].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + + } + + public static NpgsqlBox ParseBox(string s) + { + var m = boxRegex.Match(s); + return new NpgsqlBox( + new NpgsqlPoint(double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)), + new NpgsqlPoint(double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[4].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat)) + ); + } + + public static NpgsqlPath ParsePath(string s) + { + if (s[0] != '[' && s[0] != '(') + throw new Exception("Invalid path string: " + s); + + var open = s[0] == '['; + + var result = new NpgsqlPath(open); + var i = 1; + while (true) + { + var i2 = s.IndexOf(')', i); + result.Add(ParsePoint(s.Substring(i, i2 - i + 1))); + if (s[i2 + 1] != ',') + break; + i = i2 + 2; + } + return result; + } + + public static NpgsqlPolygon ParsePolygon(string s) + { + var points = new List(); + var i = 1; + while (true) + { + var i2 = s.IndexOf(')', i); + points.Add(ParsePoint(s.Substring(i, i2 - i + 1))); + if (s[i2 + 1] != ',') + break; + i = i2 + 2; + } + return new NpgsqlPolygon(points); + } + + public static NpgsqlCircle ParseCircle(string s) + { + var m = circleRegex.Match(s); + if (!m.Success) + throw new FormatException("Not a valid circle: " + s); + + return new NpgsqlCircle( + double.Parse(m.Groups[1].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[2].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat), + double.Parse(m.Groups[3].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat) + ); + } + + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/Shared.props index 32fb5d95..d90813f8 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/Shared.props @@ -8,7 +8,7 @@ - + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/Shared.props index 290ff1d1..e678cb14 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/Shared.props +++ b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/Shared.props @@ -9,6 +9,9 @@ + + + diff --git a/Extras/Core/FastReport.Plugin/Directory.Build.targets b/Extras/Core/FastReport.Plugin/Directory.Build.targets index e76b4f8b..757c5e90 100644 --- a/Extras/Core/FastReport.Plugin/Directory.Build.targets +++ b/Extras/Core/FastReport.Plugin/Directory.Build.targets @@ -3,9 +3,9 @@ - + - + \ No newline at end of file diff --git a/FastReport.Base/BandBase.Async.cs b/FastReport.Base/BandBase.Async.cs new file mode 100644 index 00000000..da0bdb29 --- /dev/null +++ b/FastReport.Base/BandBase.Async.cs @@ -0,0 +1,28 @@ +using FastReport.Utils; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public abstract partial class BandBase + { + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + + FRCollectionBase list = new FRCollectionBase(); + Objects.CopyTo(list); + foreach (ReportComponentBase obj in list) + { + await obj.GetDataAsync(cancellationToken); + obj.OnAfterData(); + + // break the component if it is of BreakableComponent an has non-empty BreakTo property + if (obj is BreakableComponent && (obj as BreakableComponent).BreakTo != null && + (obj as BreakableComponent).BreakTo.GetType() == obj.GetType()) + (obj as BreakableComponent).Break((obj as BreakableComponent).BreakTo); + } + OnAfterData(); + } + } +} diff --git a/FastReport.Base/BandBase.cs b/FastReport.Base/BandBase.cs index c2f4bd43..2fc1d12a 100644 --- a/FastReport.Base/BandBase.cs +++ b/FastReport.Base/BandBase.cs @@ -744,9 +744,6 @@ public override float CalcHeight() if (obj.GrowToBottom) { obj.Height = Height - obj.Top; - // reserve place for border - if ((!(Parent is GroupHeaderBand parent) || (parent.isLastRow && parent.DataBand == this)) && Child == null && IsLastRow && obj.Border.Lines.HasFlag(BorderLines.Bottom)) - obj.Height -= Border.BottomLine.Width; } } diff --git a/FastReport.Base/Barcode/BarcodeObject.Async.cs b/FastReport.Base/Barcode/BarcodeObject.Async.cs new file mode 100644 index 00000000..5e5e0d00 --- /dev/null +++ b/FastReport.Base/Barcode/BarcodeObject.Async.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport.Barcode +{ + public partial class BarcodeObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + #endregion + } +} diff --git a/FastReport.Base/Barcode/BarcodeObject.cs b/FastReport.Base/Barcode/BarcodeObject.cs index 0200f03a..1c49f794 100644 --- a/FastReport.Base/Barcode/BarcodeObject.cs +++ b/FastReport.Base/Barcode/BarcodeObject.cs @@ -593,6 +593,11 @@ public override void RestoreState() public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (!String.IsNullOrEmpty(DataColumn)) { object value = Report.GetColumnValue(DataColumn); diff --git a/FastReport.Base/Base.cs b/FastReport.Base/Base.cs index f362fd84..62937904 100644 --- a/FastReport.Base/Base.cs +++ b/FastReport.Base/Base.cs @@ -993,6 +993,28 @@ internal void AssignAll(Base source, bool assignName) } } + internal void AssignAll(Base source, bool assignName, bool assignAncestor) + { + Clear(); + Assign(source); + if (assignName) + SetName(source.Name); + + if (assignAncestor) + SetAncestor(source.IsAncestor); + + foreach (Base child in source.ChildObjects) + { + Base myChild = Activator.CreateInstance(child.GetType()) as Base; + myChild.SetReport(Report); + myChild.AssignAll(child, assignName, assignAncestor); + myChild.SetReport(null); + myChild.Parent = this; + if (assignAncestor) + myChild.SetAncestor(child.IsAncestor); + } + } + /// /// Gets a value indicating whether the object has the specified parent in its parent hierarchy. /// diff --git a/FastReport.Base/CheckBoxObject.Async.cs b/FastReport.Base/CheckBoxObject.Async.cs new file mode 100644 index 00000000..65947e78 --- /dev/null +++ b/FastReport.Base/CheckBoxObject.Async.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public partial class CheckBoxObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + #endregion + + } +} diff --git a/FastReport.Base/CheckBoxObject.cs b/FastReport.Base/CheckBoxObject.cs index f6df5db8..ae420540 100644 --- a/FastReport.Base/CheckBoxObject.cs +++ b/FastReport.Base/CheckBoxObject.cs @@ -340,6 +340,11 @@ public override string[] GetExpressions() public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (!String.IsNullOrEmpty(DataColumn)) { object value = Report.GetColumnValue(DataColumn); diff --git a/FastReport.Base/Code/AssemblyDescriptor.Async.cs b/FastReport.Base/Code/AssemblyDescriptor.Async.cs new file mode 100644 index 00000000..df0e99b8 --- /dev/null +++ b/FastReport.Base/Code/AssemblyDescriptor.Async.cs @@ -0,0 +1,207 @@ +using System; +#if NETSTANDARD || NETCOREAPP +using FastReport.Code.CodeDom.Compiler; +#else +using System.CodeDom.Compiler; +#endif +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using FastReport.Utils; + + +namespace FastReport.Code +{ + partial class AssemblyDescriptor : IDisposable + { + private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1); + + + private static async Task AddFastReportAssemblies(StringCollection assemblies, CancellationToken token) + { + foreach (Assembly assembly in RegisteredObjects.Assemblies) + { + string aLocation = assembly.Location; +#if CROSSPLATFORM || COREWIN + if (aLocation == "") + { + // try fix SFA in FastReport.Compat + string fixedReference = await CodeDomProvider.TryFixReferenceInSingeFileAppAsync(assembly, token); + if (!string.IsNullOrEmpty(fixedReference)) + aLocation = fixedReference; + } +#endif + if (!ContainsAssembly(assemblies, aLocation)) + assemblies.Add(aLocation); + } + } + + public async Task CompileAsync(CancellationToken token = default) + { + if (needCompile) + { + await semaphoreSlim.WaitAsync(token); + + if (needCompile) + { + try + { + await InternalCompileAsync(token); + } + finally + { + semaphoreSlim.Release(); + } + } + } + } + + + private async Task InternalCompileAsync(CancellationToken cancellationToken) + { + CompilerParameters cp = await GetCompilerParametersAsync(cancellationToken); + + if (Config.WebMode && + Config.EnableScriptSecurity && + Config.ScriptSecurityProps.AddStubClasses) + AddStubClasses(); + + string errors = string.Empty; + CompilerResults cr = await InternalCompileAsync(cp, cancellationToken); + bool success = CheckCompileResult(cr); + for (int i = 0; !success && i < Config.CompilerSettings.RecompileCount; i++) + { + cr = await TryRecompileAsync(cp, cr, cancellationToken); + success = CheckCompileResult(cr); + } + + if (cr != null) + HandleCompileErrors(cr, out errors); + + if (!success && errors != string.Empty) + throw new CompilerException(errors); + } + + private static bool CheckCompileResult(CompilerResults result) + { + // if result == null => was found in cache + return result == null || result.Errors.Count == 0; + } + + + private async Task GetCompilerParametersAsync(CancellationToken ct) + { + // configure compiler options + CompilerParameters cp = new CompilerParameters(); + await AddFastReportAssemblies(cp.ReferencedAssemblies, ct); // 2 + AddReferencedAssemblies(cp.ReferencedAssemblies, currentFolder); // 9 + ReviewReferencedAssemblies(cp.ReferencedAssemblies); + cp.GenerateInMemory = true; + // sometimes the system temp folder is not accessible... + if (Config.TempFolder != null) + cp.TempFiles = new TempFileCollection(Config.TempFolder, false); + return cp; + } + + /// + /// Returns true, if compilation is successful + /// + private async Task InternalCompileAsync(CompilerParameters cp, CancellationToken cancellationToken) + { + CompilerResults cr; + // find assembly in cache + string assemblyHash = GetAssemblyHash(cp); + Assembly cachedAssembly; + if (FAssemblyCache.TryGetValue(assemblyHash, out cachedAssembly)) + { + Assembly = cachedAssembly; + var reportScript = Assembly.CreateInstance("FastReport.ReportScript"); + InitInstance(reportScript); + cr = null; + return cr; // return true; + } + + // compile report scripts + using (CodeDomProvider provider = Report.CodeHelper.GetCodeProvider()) + { + string script = scriptText.ToString(); + ScriptSecurityEventArgs ssea = new ScriptSecurityEventArgs(Report, script, Report.ReferencedAssemblies); + Config.OnScriptCompile(ssea); + +#if CROSSPLATFORM || COREWIN + provider.BeforeEmitCompilation += Config.OnBeforeScriptCompilation; + + cr = await provider.CompileAssemblyFromSourceAsync(cp, script, Config.CompilerSettings.CultureInfo, cancellationToken); +#else + cr = provider.CompileAssemblyFromSource(cp, script); +#endif + Assembly = null; + Instance = null; + + if (cr.Errors.Count != 0) // Compile errors + return cr; // return false; + + FAssemblyCache.TryAdd(assemblyHash, cr.CompiledAssembly); + + Assembly = cr.CompiledAssembly; + var reportScript = Assembly.CreateInstance("FastReport.ReportScript"); + InitInstance(reportScript); + return cr; + } + } + + + /// + /// Returns true if recompilation is successful + /// + private async Task TryRecompileAsync(CompilerParameters cp, CompilerResults oldResult, CancellationToken ct) + { + List additionalAssemblies = new List(4); + + foreach (CompilerError ce in oldResult.Errors) + { + if (ce.ErrorNumber == "CS0012") // missing reference on assembly + { + // try to add reference + try + { + // in .Net Core compiler will return other quotes +#if CROSSPLATFORM || COREWIN + const string quotes = "\'"; +#else + const string quotes = "\""; +#endif + const string pattern = quotes + @"(\S{1,}),"; + Regex regex = new Regex(pattern, RegexOptions.Compiled); + string assemblyName = regex.Match(ce.ErrorText).Groups[1].Value; // Groups[1] include string without quotes and , symbols + if (!additionalAssemblies.Contains(assemblyName)) + additionalAssemblies.Add(assemblyName); + continue; + } + catch { } + } + } + + if (additionalAssemblies.Count > 0) // need recompile + { + // try to load missing assemblies + foreach (string assemblyName in additionalAssemblies) + { + AddReferencedAssembly(cp.ReferencedAssemblies, currentFolder, assemblyName); + } + + return await InternalCompileAsync(cp, ct); + } + + return oldResult; + } + + public void Dispose() + { + semaphoreSlim.Dispose(); + } + } +} diff --git a/FastReport.Base/Code/AssemblyDescriptor.cs b/FastReport.Base/Code/AssemblyDescriptor.cs index 67981c65..9a0edbbb 100644 --- a/FastReport.Base/Code/AssemblyDescriptor.cs +++ b/FastReport.Base/Code/AssemblyDescriptor.cs @@ -13,8 +13,6 @@ using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; using System.Collections.Concurrent; using FastReport.Data; using FastReport.Engine; @@ -25,7 +23,7 @@ namespace FastReport.Code { - partial class AssemblyDescriptor : IDisposable + partial class AssemblyDescriptor { private static readonly ConcurrentDictionary FAssemblyCache; private readonly StringBuilder scriptText; @@ -36,9 +34,6 @@ partial class AssemblyDescriptor : IDisposable private const string shaKey = "FastReportCode"; private readonly static object compileLocker; private readonly string currentFolder; -#if ASYNC - private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1); -#endif public Assembly Assembly { get; private set; } @@ -169,27 +164,6 @@ private static void AddFastReportAssemblies(StringCollection assemblies) } } -#if ASYNC - private static async ValueTask AddFastReportAssemblies(StringCollection assemblies, CancellationToken token) - { - foreach (Assembly assembly in RegisteredObjects.Assemblies) - { - string aLocation = assembly.Location; -#if CROSSPLATFORM || COREWIN - if (aLocation == "") - { - // try fix SFA in FastReport.Compat - string fixedReference = await CodeDomProvider.TryFixReferenceInSingeFileAppAsync(assembly, token); - if (!string.IsNullOrEmpty(fixedReference)) - aLocation = fixedReference; - } -#endif - if (!ContainsAssembly(assemblies, aLocation)) - assemblies.Add(aLocation); - } - } -#endif - private void AddReferencedAssemblies(StringCollection assemblies, string defaultPath) { for (int i = 0; i < Report.ReferencedAssemblies.Length; i++) @@ -414,27 +388,6 @@ public void Compile() } } -#if ASYNC - public async Task CompileAsync(CancellationToken token = default) - { - if (needCompile) - { - await semaphoreSlim.WaitAsync(token); - - if (needCompile) - { - try - { - await InternalCompileAsync(token); - } - finally - { - semaphoreSlim.Release(); - } - } - } - } -#endif private void InternalCompile() { @@ -460,39 +413,6 @@ private void InternalCompile() throw new CompilerException(errors); } -#if ASYNC - private async Task InternalCompileAsync(CancellationToken cancellationToken) - { - CompilerParameters cp = await GetCompilerParametersAsync(cancellationToken); - - if (Config.WebMode && - Config.EnableScriptSecurity && - Config.ScriptSecurityProps.AddStubClasses) - AddStubClasses(); - - string errors = string.Empty; - CompilerResults cr = await InternalCompileAsync(cp, cancellationToken); - bool success = CheckCompileResult(cr); - for (int i = 0; !success && i < Config.CompilerSettings.RecompileCount; i++) - { - cr = await TryRecompileAsync(cp, cr, cancellationToken); - success = CheckCompileResult(cr); - } - - if (cr != null) - HandleCompileErrors(cr, out errors); - - if (!success && errors != string.Empty) - throw new CompilerException(errors); - } - - private static bool CheckCompileResult(CompilerResults result) - { - // if result == null => was found in cache - return result == null || result.Errors.Count == 0; - } -#endif - private CompilerParameters GetCompilerParameters() { // configure compiler options @@ -507,21 +427,6 @@ private CompilerParameters GetCompilerParameters() return cp; } -#if ASYNC - private async Task GetCompilerParametersAsync(CancellationToken ct) - { - // configure compiler options - CompilerParameters cp = new CompilerParameters(); - await AddFastReportAssemblies(cp.ReferencedAssemblies, ct); // 2 - AddReferencedAssemblies(cp.ReferencedAssemblies, currentFolder); // 9 - ReviewReferencedAssemblies(cp.ReferencedAssemblies); - cp.GenerateInMemory = true; - // sometimes the system temp folder is not accessible... - if (Config.TempFolder != null) - cp.TempFiles = new TempFileCollection(Config.TempFolder, false); - return cp; - } -#endif private string GetAssemblyHash(CompilerParameters cp) { @@ -588,51 +493,6 @@ private bool TryInternalCompile(CompilerParameters cp, out CompilerResults cr) } } -#if ASYNC && (CROSSPLATFORM || COREWIN) - /// - /// Returns true, if compilation is successful - /// - private async ValueTask InternalCompileAsync(CompilerParameters cp, CancellationToken cancellationToken) - { - CompilerResults cr; - // find assembly in cache - string assemblyHash = GetAssemblyHash(cp); - Assembly cachedAssembly; - if (FAssemblyCache.TryGetValue(assemblyHash, out cachedAssembly)) - { - Assembly = cachedAssembly; - var reportScript = Assembly.CreateInstance("FastReport.ReportScript"); - InitInstance(reportScript); - cr = null; - return cr; // return true; - } - - // compile report scripts - using (CodeDomProvider provider = Report.CodeHelper.GetCodeProvider()) - { - string script = scriptText.ToString(); - ScriptSecurityEventArgs ssea = new ScriptSecurityEventArgs(Report, script, Report.ReferencedAssemblies); - Config.OnScriptCompile(ssea); - - provider.BeforeEmitCompilation += Config.OnBeforeScriptCompilation; - - cr = await provider.CompileAssemblyFromSourceAsync(cp, script, Config.CompilerSettings.CultureInfo, cancellationToken); - Assembly = null; - Instance = null; - - if (cr.Errors.Count != 0) // Compile errors - return cr; // return false; - - FAssemblyCache.TryAdd(assemblyHash, cr.CompiledAssembly); - - Assembly = cr.CompiledAssembly; - var reportScript = Assembly.CreateInstance("FastReport.ReportScript"); - InitInstance(reportScript); - return cr; - } - } -#endif - private string ReplaceExpression(string error, TextObjectBase text) { @@ -811,53 +671,6 @@ private bool TryRecompile(CompilerParameters cp, ref CompilerResults cr) return false; } -#if ASYNC - /// - /// Returns true if recompilation is successful - /// - private async Task TryRecompileAsync(CompilerParameters cp, CompilerResults oldResult, CancellationToken ct) - { - List additionalAssemblies = new List(4); - - foreach (CompilerError ce in oldResult.Errors) - { - if (ce.ErrorNumber == "CS0012") // missing reference on assembly - { - // try to add reference - try - { - // in .Net Core compiler will return other quotes -#if CROSSPLATFORM || COREWIN - const string quotes = "\'"; -#else - const string quotes = "\""; -#endif - const string pattern = quotes + @"(\S{1,}),"; - Regex regex = new Regex(pattern, RegexOptions.Compiled); - string assemblyName = regex.Match(ce.ErrorText).Groups[1].Value; // Groups[1] include string without quotes and , symbols - if (!additionalAssemblies.Contains(assemblyName)) - additionalAssemblies.Add(assemblyName); - continue; - } - catch { } - } - } - - if (additionalAssemblies.Count > 0) // need recompile - { - // try to load missing assemblies - foreach (string assemblyName in additionalAssemblies) - { - AddReferencedAssembly(cp.ReferencedAssemblies, currentFolder, assemblyName); - } - - return await InternalCompileAsync(cp, ct); - } - - return oldResult; - } -#endif - public void InitInstance(object instance) { this.Instance = instance; @@ -900,12 +713,6 @@ public object InvokeMethod(string name, object[] parms) } } - public void Dispose() - { -#if ASYNC - semaphoreSlim.Dispose(); -#endif - } public AssemblyDescriptor(Report report, string scriptText) { diff --git a/FastReport.Base/ContainerObject.Async.cs b/FastReport.Base/ContainerObject.Async.cs new file mode 100644 index 00000000..11de54fe --- /dev/null +++ b/FastReport.Base/ContainerObject.Async.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport +{ + /// + /// Container object that may contain child objects. + /// + public partial class ContainerObject : ReportComponentBase, IParent + { + #region Report engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + foreach (ReportComponentBase obj in Objects) + { + await obj.GetDataAsync(cancellationToken); + obj.OnAfterData(); + + // break the component if it is of BreakableComponent an has non-empty BreakTo property + if (obj is BreakableComponent && (obj as BreakableComponent).BreakTo != null && + (obj as BreakableComponent).BreakTo.GetType() == obj.GetType()) + (obj as BreakableComponent).Break((obj as BreakableComponent).BreakTo); + } + } + + #endregion + } +} diff --git a/FastReport.Base/CrossView/CrossViewObject.Async.cs b/FastReport.Base/CrossView/CrossViewObject.Async.cs new file mode 100644 index 00000000..4c5335c7 --- /dev/null +++ b/FastReport.Base/CrossView/CrossViewObject.Async.cs @@ -0,0 +1,24 @@ +using System; +using FastReport.Table; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport.CrossView +{ + /// + /// Represents the crossview object that is used to print cube slice or slicegrid. + /// + public partial class CrossViewObject : TableBase + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion + } +} diff --git a/FastReport.Base/CrossView/CrossViewObject.cs b/FastReport.Base/CrossView/CrossViewObject.cs index f33d8902..3aa72a8b 100644 --- a/FastReport.Base/CrossView/CrossViewObject.cs +++ b/FastReport.Base/CrossView/CrossViewObject.cs @@ -443,8 +443,12 @@ public override void SaveState() /// public override void GetData() { - base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (Data.SourceAssigned) { diff --git a/FastReport.Base/Data/DataConnectionBase.cs b/FastReport.Base/Data/DataConnectionBase.cs index 93bf2750..184c9bea 100644 --- a/FastReport.Base/Data/DataConnectionBase.cs +++ b/FastReport.Base/Data/DataConnectionBase.cs @@ -207,7 +207,7 @@ private void GetDBObjectNames(string name, List list) } } - private string PrepareSelectCommand(string selectCommand, string tableName, DbConnection connection) + protected string PrepareSelectCommand(string selectCommand, string tableName, DbConnection connection) { if (String.IsNullOrEmpty(selectCommand)) { @@ -216,7 +216,7 @@ private string PrepareSelectCommand(string selectCommand, string tableName, DbCo return selectCommand; } - private TableDataSource FindTableDataSource(DataTable table) + protected TableDataSource FindTableDataSource(DataTable table) { foreach (TableDataSource c in Tables) { diff --git a/FastReport.Base/Data/Dictionary.cs b/FastReport.Base/Data/Dictionary.cs index 2c731e4f..edbc88d9 100644 --- a/FastReport.Base/Data/Dictionary.cs +++ b/FastReport.Base/Data/Dictionary.cs @@ -237,7 +237,7 @@ internal void RegisterDataTable(DataTable table, string referenceName, bool enab /// /// The DataView to register. /// The name of the data object. - /// Determines wheter to enable the object or not. + /// Determines whether to enable the object or not. /// /// This method is for internal use only. /// @@ -650,10 +650,13 @@ public override void Serialize(FRWriter writer) { writer.ItemName = ClassName; ObjectCollection childObjects = ChildObjects; - foreach (Base c in childObjects) + if (writer.SaveChildren) { - if (c is Parameter || c is Total || c is CubeSourceBase || (c is DataComponentBase && (c as DataComponentBase).Enabled)) - writer.Write(c); + foreach (Base c in childObjects) + { + if (c is Parameter || c is Total || c is CubeSourceBase || (c is DataComponentBase && (c as DataComponentBase).Enabled)) + writer.Write(c); + } } } @@ -720,6 +723,16 @@ public void Load(string fileName) /// /// Another dictionary to merge the data from. public void Merge(Dictionary source) + { + Merge(source, false); + } + + /// + /// Merges this dictionary with another Dictionary. + /// + /// Another dictionary to merge the data from. + /// Determines whether to disable the merge of the totals and parameters. + public void Merge(Dictionary source, bool mergeOnlyDataSource) { // Report object is needed to handle save/load of dictionary correctly. // Some dictionary objects (such as relations) may contain references to other objects. @@ -733,18 +746,27 @@ public void Merge(Dictionary source) foreach (Base c in clone.ChildObjects) { + if ((c is Total || c is Parameter) && mergeOnlyDataSource) + continue; + Base my = FindByName(c.Name); if (my != null) { - foreach (PageBase page in Report.Pages) + if (c is DataSourceBase dataSourceBase) { - if (page is ReportPage) - foreach (Base band in (page as ReportPage).AllObjects) + foreach (PageBase page in Report.Pages) + { + if (page is ReportPage) { - if (band is DataBand dataBand && dataBand.DataSource != null && (my.AllObjects.Contains(dataBand.DataSource) || dataBand.DataSource.Name == my.Name - || dataBand.DataSource == my)) - dataBand.DataSource = (DataSourceBase)clone.FindByName(dataBand.DataSource.Name); + foreach (Base obj in (page as ReportPage).AllObjects) + { + if (obj is IContainDataSource dataObj) + { + dataObj.UpdateDataSourceRef(dataSourceBase); + } + } } + } } my.Dispose(); } @@ -755,7 +777,7 @@ public void Merge(Dictionary source) ReRegisterData(); } } - #endregion +#endregion #region IParent Members /// diff --git a/FastReport.Base/DataBand.cs b/FastReport.Base/DataBand.cs index 95562db7..855af16c 100644 --- a/FastReport.Base/DataBand.cs +++ b/FastReport.Base/DataBand.cs @@ -15,7 +15,7 @@ namespace FastReport /// property if you want to filter data rows. The /// property can be used to sort data rows. /// - public partial class DataBand : BandBase + public partial class DataBand : BandBase, IContainDataSource { #region Fields private DataHeaderBand header; @@ -351,6 +351,14 @@ private void DataSource_Disposed(object sender, EventArgs e) { dataSource = null; } + + void IContainDataSource.UpdateDataSourceRef(DataSourceBase newRefDatasource) + { + if (newRefDatasource != null && (newRefDatasource.Name == DataSource?.Name || newRefDatasource == DataSource)) + { + DataSource = newRefDatasource; + } + } #endregion #region Protected Methods diff --git a/FastReport.Base/Engine/ReportEngine.Async.cs b/FastReport.Base/Engine/ReportEngine.Async.cs new file mode 100644 index 00000000..c8252d1b --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.Async.cs @@ -0,0 +1,72 @@ +using FastReport.Utils; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Engine +{ + /// + /// Represents the report engine. + /// + public partial class ReportEngine + { + + #region Private Methods + + private async Task RunReportPagesAsync(ReportPage page, CancellationToken cancellationToken) + { + OnStateChanged(Report, EngineState.ReportStarted); + + if (page == null) + await RunReportPagesAsync(cancellationToken); + else + await RunReportPageAsync(page, cancellationToken); + + OnStateChanged(Report, EngineState.ReportFinished); + } + + #endregion Private Methods + + #region Internal Methods + + + internal Task RunAsync(bool runDialogs, bool append, bool resetDataState, CancellationToken cancellationToken) + { + return RunAsync(runDialogs, append, resetDataState, null, cancellationToken); + } + + internal async Task RunAsync(bool runDialogs, bool append, bool resetDataState, ReportPage page, CancellationToken cancellationToken) + { + try + { + RunPhase1(resetDataState); + if (runDialogs && !(await RunDialogsAsync())) + return false; + await RunPhase2Async(append, page, cancellationToken); + } + finally + { + RunFinished(); + } + return true; + } + + internal async Task RunPhase2Async(bool append, ReportPage page, CancellationToken cancellationToken) + { + Config.ReportSettings.OnStartProgress(Report); + PrepareToFirstPass(append); + await RunReportPagesAsync(page, cancellationToken); + + ResetLogicalPageNumber(); + if (Report.DoublePass && !Report.Aborted) + { + finalPass = true; + PrepareToSecondPass(); + await RunReportPagesAsync(page, cancellationToken); + } + } + + #endregion Internal Methods + } +} diff --git a/FastReport.Base/Engine/ReportEngine.Bands.Async.cs b/FastReport.Base/Engine/ReportEngine.Bands.Async.cs new file mode 100644 index 00000000..3ed3615e --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.Bands.Async.cs @@ -0,0 +1,316 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Engine +{ + public partial class ReportEngine + { + #region Private Methods + + private async Task PrepareBandAsync(BandBase band, bool getData, CancellationToken cancellationToken) + { + if (band.Visible) + { + if (getData) + { + await band.GetDataAsync(cancellationToken); + } + PrepareBandShared(band); + } + } + + private async Task CalcHeightAsync(BandBase band, CancellationToken cancellationToken) + { + // band is already prepared, its Height is ready to use + if (band.IsRunning) + return band.Height; + + band.SaveState(); + try + { + await PrepareBandAsync(band, true, cancellationToken); + return band.Height; + } + finally + { + band.RestoreState(); + } + } + + private async Task AddToOutputBandAsync(BandBase band, bool getData, CancellationToken cancellationToken) + { + band.SaveState(); + + try + { + await PrepareBandAsync(band, getData, cancellationToken); + + if (band.Visible) + { + outputBand.SetRunning(true); + + BandBase cloneBand = CloneBand(band); + cloneBand.Left = CurX; + cloneBand.Top = CurY; + cloneBand.Parent = outputBand; + + CurY += cloneBand.Height; + } + } + finally + { + band.RestoreState(); + } + } + + private async Task ShowBandToPreparedPagesAsync(BandBase band, bool getData, CancellationToken cancellationToken) + { + // handle "StartNewPage". Skip if it's the first row, avoid empty first page. + if ((band.StartNewPage && !(band.Parent is PageHeaderBand || band.Parent is PageFooterBand)) && band.FlagUseStartNewPage && (band.RowNo != 1 || band.FirstRowStartsNewPage) && + !band.Repeated) + { + EndColumn(); + } + + band.SaveState(); + try + { + await PrepareBandAsync(band, getData, cancellationToken); + + if (band.Visible) + { + if (BandHasHardPageBreaks(band)) + { + foreach (var b in SplitHardPageBreaks(band)) + { + if (b.StartNewPage) + EndColumn(); + await AddToPreparedPagesAsync(b, cancellationToken); + } + } + else + { + await AddToPreparedPagesAsync(band, cancellationToken); + } + } + } + finally + { + band.RestoreState(); + } + } + + + public async Task ShowBandAsync(BandBase band, CancellationToken cancellationToken) + { + if (band != null) + for (int i = 0; i < band.RepeatBandNTimes; i++) + await ShowBandAsync(band, true, cancellationToken); + } + + private async Task ShowBandAsync(BandBase band, bool getData, CancellationToken cancellationToken) + { + if (band == null) + return; + + BandBase saveCurBand = curBand; + curBand = band; + + try + { + // do we need to keep child? + ChildBand child = band.Child; + bool showChild = child != null && !(band is DataBand && child.CompleteToNRows > 0) && !child.FillUnusedSpace && + !(band is DataBand && child.PrintIfDatabandEmpty); + if (showChild && band.KeepChild) + { + StartKeep(band); + } + + if (outputBand != null) + { + await AddToOutputBandAsync(band, getData, cancellationToken); + } + else + { + await ShowBandToPreparedPagesAsync(band, getData, cancellationToken); + } + + ProcessTotals(band); + if (band.Visible) + { + await RenderOuterSubreportsAsync(band, cancellationToken); + } + + // show child band. Skip if child is used to fill empty space: it was processed already + if (showChild) + { + await ShowBandAsync(child, cancellationToken); + if (band.KeepChild) + { + EndKeep(); + } + } + } + finally + { + curBand = saveCurBand; + } + } + + internal async Task AddToPreparedPagesAsync(BandBase band, CancellationToken cancellationToken) + { + bool isReportSummary = band is ReportSummaryBand; + + // check if band is service band (e.g. page header/footer/overlay). + BandBase mainBand = band; + // for child bands, check its parent band. + if (band is ChildBand) + { + mainBand = (band as ChildBand).GetTopParentBand; + } + bool isPageBand = mainBand is PageHeaderBand || mainBand is PageFooterBand || mainBand is OverlayBand; + bool isColumnBand = mainBand is ColumnHeaderBand || mainBand is ColumnFooterBand; + + // check if we have enough space for a band. + bool checkFreeSpace = !isPageBand && !isColumnBand && band.FlagCheckFreeSpace; + if (checkFreeSpace && FreeSpace < band.Height) + { + // we don't have enough space. What should we do? + // - if band can break, break it + // - if band cannot break, check the band height: + // - it's the first row of a band and is bigger than page: break it immediately. + // - in other case, add a new page/column and tell the band that it must break next time. + if (band.CanBreak || band.FlagMustBreak || (band.AbsRowNo == 1 && band.Height > PageHeight - PageFooterHeight)) + { + // since we don't show the column footer band in the EndLastPage, do it here. + if (isReportSummary) + { + ShowReprintFooters(); + await ShowBandAsync(page.ColumnFooter, cancellationToken); + } + BreakBand(band); + return; + } + else + { + EndColumn(); + band.FlagMustBreak = true; + await AddToPreparedPagesAsync(band, cancellationToken); + band.FlagMustBreak = false; + return; + } + } + else + { + // since we don't show the column footer band in the EndLastPage, do it here. + if (isReportSummary) + { + if ((band as ReportSummaryBand).KeepWithData) + { + EndKeep(); + } + ShowReprintFooters(false); + await ShowBandAsync(page.ColumnFooter, cancellationToken); + } + } + + // check if we have a child band with FillUnusedSpace flag + if (band.Child != null && band.Child.FillUnusedSpace) + { + // if we reprint a data/group footer, do not include the band height into calculation: + // it is already counted in FreeSpace + float bandHeight = band.Height; + if (band.Repeated) + { + bandHeight = 0; + } + while (FreeSpace - bandHeight - band.Child.Height >= 0) + { + float saveCurY = CurY; + await ShowBandAsync(band.Child, cancellationToken); + // nothing was printed, break to avoid an endless loop + if (CurY == saveCurY) + { + break; + } + } + } + + // adjust the band location + if (band is PageFooterBand && !UnlimitedHeight) + { + CurY = PageHeight - GetBandHeightWithChildren(band); + } + if (!isPageBand) + { + band.Left += originX + CurX; + } + if (band.PrintOnBottom) + { + CurY = PageHeight - PageFooterHeight - ColumnFooterHeight; + // if PrintOnBottom is applied to a band like DataFooter, print it with all its child bands + // if PrintOnBottom is applied to a child band, print this band only. + if (band is ChildBand) + { + CurY -= band.Height; + } + else + { + CurY -= GetBandHeightWithChildren(band); + } + } + band.Top = CurY; + + // shift the band and decrease its width when printing hierarchy + float saveLeft = band.Left; + float saveWidth = band.Width; + if (!isPageBand && !isColumnBand) + { + band.Left += hierarchyIndent; + band.Width -= hierarchyIndent; + } + + // add outline + AddBandOutline(band); + + // add bookmarks + band.AddBookmarks(); + + // put the band to prepared pages. Do not put page bands twice + // (this may happen when we render a subreport, or append a report to another one). + bool bandAdded = true; + bool bandAlreadyExists = false; + if (isPageBand) + { + if (band is ChildBand) + { + bandAlreadyExists = PreparedPages.ContainsBand(band.Name); + } + else + { + bandAlreadyExists = PreparedPages.ContainsBand(band.GetType()); + } + } + + if (!bandAlreadyExists) + { + bandAdded = PreparedPages.AddBand(band); + } + + // shift CurY + if (bandAdded && !(mainBand is OverlayBand)) + { + CurY += band.Height; + } + + // set left&width back + band.Left = saveLeft; + band.Width = saveWidth; + } + + #endregion Private Methods + } +} diff --git a/FastReport.Base/Engine/ReportEngine.Bands.cs b/FastReport.Base/Engine/ReportEngine.Bands.cs index 53dd5d1a..4a6c5a21 100644 --- a/FastReport.Base/Engine/ReportEngine.Bands.cs +++ b/FastReport.Base/Engine/ReportEngine.Bands.cs @@ -21,12 +21,17 @@ private void PrepareBand(BandBase band, bool getData) { band.GetData(); } - TranslateObjects(band); - RenderInnerSubreports(band); - band.CalcHeight(); + PrepareBandShared(band); } } + private void PrepareBandShared(BandBase band) + { + TranslateObjects(band); + RenderInnerSubreports(band); + band.CalcHeight(); + } + private float CalcHeight(BandBase band) { // band is already prepared, its Height is ready to use diff --git a/FastReport.Base/Engine/ReportEngine.DataBands.Async.cs b/FastReport.Base/Engine/ReportEngine.DataBands.Async.cs new file mode 100644 index 00000000..a458dc4c --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.DataBands.Async.cs @@ -0,0 +1,291 @@ +using FastReport.Data; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Engine +{ + public partial class ReportEngine + { + #region Private Methods + + private async Task RunDataBandAsync(DataBand dataBand, CancellationToken cancellationToken) + { + if (page.Columns.Count > 1 && Report.Engine.UnlimitedHeight) + dataBand.Columns.Count = page.Columns.Count; + + dataBand.InitDataSource(); + dataBand.DataSource.First(); + + int rowCount = dataBand.DataSource.RowCount; + if (dataBand.IsDatasourceEmpty && dataBand.PrintIfDatasourceEmpty) + rowCount = 1; + if (dataBand.CollectChildRows && rowCount > 1) + rowCount = 1; + if (dataBand.MaxRows > 0 && rowCount > dataBand.MaxRows) + rowCount = dataBand.MaxRows; + + bool keepFirstRow = NeedKeepFirstRow(dataBand); + bool keepLastRow = NeedKeepLastRow(dataBand); + + await RunDataBandAsync(dataBand, rowCount, keepFirstRow, keepLastRow, cancellationToken); + + // do not leave the datasource in EOF state to allow print something in the footer + dataBand.DataSource.Prior(); + } + + private async Task RunDataBandAsync(DataBand dataBand, int rowCount, bool keepFirstRow, bool keepLastRow, CancellationToken cancellationToken) + { + if (dataBand.IsHierarchical) + { + await ShowHierarchyAsync(dataBand, rowCount, cancellationToken); + return; + } + + bool isFirstRow = true; + bool someRowsPrinted = false; + dataBand.RowNo = 0; + dataBand.IsFirstRow = false; + dataBand.IsLastRow = false; + + // check if we have only one data row that should be kept with both header and footer + bool oneRow = rowCount == 1 && keepFirstRow && keepLastRow; + + // cycle through records + for (int i = 0; i < rowCount; i++) + { + bool isLastRow = i == rowCount - 1; + if (!dataBand.IsDetailEmpty()) + { + dataBand.RowNo++; + dataBand.AbsRowNo++; + dataBand.IsFirstRow = isFirstRow; + dataBand.IsLastRow = isLastRow; + + // keep header + if (isFirstRow && keepFirstRow) + StartKeep(dataBand); + + // keep together + if (isFirstRow && dataBand.KeepTogether) + StartKeep(dataBand); + + // keep detail + if (dataBand.KeepDetail) + StartKeep(dataBand); + + // show header + if (isFirstRow) + await ShowDataHeaderAsync(dataBand, cancellationToken); + + // keep footer + if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand) + StartKeep(dataBand); + + // start block event + if (isFirstRow) + OnStateChanged(dataBand, EngineState.BlockStarted); + + // show band + await ShowDataBandAsync(dataBand, rowCount, cancellationToken); + + // end keep header + if (isFirstRow && keepFirstRow && !oneRow) + EndKeep(); + + // end keep footer + if (isLastRow && keepLastRow && dataBand.IsDeepmostDataBand) + CheckKeepFooter(dataBand); + + // show sub-bands + await RunBandsAsync(dataBand.Bands, cancellationToken); + + // up the outline + OutlineUp(dataBand); + + // end keep detail + if (dataBand.KeepDetail) + EndKeep(); + + isFirstRow = false; + someRowsPrinted = true; + + if (dataBand.Columns.Count > 1) + break; + } + + dataBand.DataSource.Next(); + if (Report.Aborted) + break; + } + + // complete upto N rows + ChildBand child = dataBand.Child; + if (child != null && child.CompleteToNRows > rowCount) + { + for (int i = 0; i < child.CompleteToNRows - rowCount; i++) + { + child.RowNo = rowCount + i + 1; + child.AbsRowNo = rowCount + i + 1; + await ShowBandAsync(child, cancellationToken); + } + } + // print child if databand is empty + if (child != null && child.PrintIfDatabandEmpty && dataBand.IsDatasourceEmpty) + { + await ShowBandAsync(child, cancellationToken); + } + + if (someRowsPrinted) + { + // finish block event + OnStateChanged(dataBand, EngineState.BlockFinished); + + // show footer + await ShowDataFooterAsync(dataBand, cancellationToken); + + // end KeepTogether + if (dataBand.KeepTogether) + EndKeep(); + + // end KeepLastRow + if (keepLastRow) + EndKeep(); + } + } + + private async Task ShowDataBandAsync(DataBand dataBand, int rowCount, CancellationToken cancellationToken) + { + if (dataBand.Columns.Count > 1) + { + dataBand.Width = dataBand.Columns.ActualWidth; + await RenderMultiColumnBandAsync(dataBand, rowCount, cancellationToken); + } + else + { + if (dataBand.ResetPageNumber && (dataBand.FirstRowStartsNewPage || dataBand.RowNo > 1)) + ResetLogicalPageNumber(); + if (dataBand.Footer != null && dataBand.CanBreak) + if (dataBand.Footer.KeepWithData && dataBand.Footer.Height + dataBand.Height > FreeSpace) + { + dataBand.AddLastToFooter(dataBand.Footer); + } + await ShowBandAsync(dataBand, cancellationToken); + } + } + + private async Task RenderMultiColumnBandAsync(DataBand dataBand, int rowCount, CancellationToken cancellationToken) + { + if (dataBand.Columns.Layout == ColumnLayout.AcrossThenDown) + RenderBandAcrossThenDown(dataBand, rowCount); + else + { + DataSourceBase dataSource = dataBand.DataSource; + int saveRow = dataSource.CurrentRowNo; + + // calc height of each data row. This list is shared across RenderBandDownThenAcross calls. + Hashtable heights = new Hashtable(); + for (int i = 0; i < rowCount; i++) + { + dataSource.CurrentRowNo = i + saveRow; + heights[i + saveRow] = await CalcHeightAsync(dataBand, cancellationToken); + } + + dataSource.CurrentRowNo = saveRow; + while (rowCount > 0) + { + rowCount = RenderBandDownThenAcross(dataBand, rowCount, heights); + } + } + } + + private async Task ShowHierarchyAsync(DataBand dataBand, HierarchyItem rootItem, int level, string fullRowNo, CancellationToken cancellationToken) + { + int saveLevel = hierarchyLevel; + float saveIndent = hierarchyIndent; + string saveRowNo = hierarchyRowNo; + hierarchyLevel = level; + hierarchyIndent = dataBand.Indent * (level - 1); + + try + { + if (rootItem.items.Count > 0) + { + await ShowBandAsync(dataBand.Header, cancellationToken); + + int rowNo = 0; + foreach (HierarchyItem item in rootItem.items) + { + rowNo++; + dataBand.RowNo = rowNo; + dataBand.AbsRowNo++; + dataBand.DataSource.CurrentRowNo = item.rowNo; + hierarchyRowNo = fullRowNo + rowNo.ToString(); + + // show the main hierarchy band + await ShowBandAsync(dataBand, cancellationToken); + + // show sub-bands if any + await RunBandsAsync(dataBand.Bands, cancellationToken); + + await ShowHierarchyAsync(dataBand, item, level + 1, hierarchyRowNo + ".", cancellationToken); + + // up the outline + OutlineUp(dataBand); + } + + await ShowBandAsync(dataBand.Footer, cancellationToken); + } + } + finally + { + hierarchyLevel = saveLevel; + hierarchyIndent = saveIndent; + hierarchyRowNo = saveRowNo; + } + } + + private async Task ShowHierarchyAsync(DataBand dataBand, int rowCount, CancellationToken cancellationToken) + { + HierarchyItem rootItem = MakeHierarchy(dataBand, rowCount); + if (rootItem == null) + return; + + await ShowHierarchyAsync(dataBand, rootItem, 1, "", cancellationToken); + } + + private async Task ShowDataHeaderAsync(DataBand dataBand, CancellationToken cancellationToken) + { + DataHeaderBand header = dataBand.Header; + if (header != null) + { + await ShowBandAsync(header, cancellationToken); + if (header.RepeatOnEveryPage) + AddReprint(header); + } + + DataFooterBand footer = dataBand.Footer; + if (footer != null) + { + if (footer.RepeatOnEveryPage) + AddReprint(footer); + } + } + + private async Task ShowDataFooterAsync(DataBand dataBand, CancellationToken cancellationToken) + { + dataBand.DataSource.Prior(); + + DataFooterBand footer = dataBand.Footer; + RemoveReprint(footer); + await ShowBandAsync(footer, cancellationToken); + RemoveReprint(dataBand.Header); + + dataBand.DataSource.Next(); + } + + #endregion Private Methods + } +} diff --git a/FastReport.Base/Engine/ReportEngine.Groups.Async.cs b/FastReport.Base/Engine/ReportEngine.Groups.Async.cs new file mode 100644 index 00000000..4159b6fe --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.Groups.Async.cs @@ -0,0 +1,162 @@ +using FastReport.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Engine +{ + public partial class ReportEngine + { + #region Private Methods + + private async Task ShowDataHeaderAsync(GroupHeaderBand groupBand, CancellationToken cancellationToken) + { + groupBand.RowNo = 0; + + DataHeaderBand header = groupBand.Header; + if (header != null) + { + await ShowBandAsync(header, cancellationToken); + if (header.RepeatOnEveryPage) + AddReprint(header); + } + + DataFooterBand footer = groupBand.Footer; + if (footer != null) + { + if (footer.RepeatOnEveryPage) + AddReprint(footer); + } + } + + private async Task ShowDataFooterAsync(GroupHeaderBand groupBand, CancellationToken cancellationToken) + { + DataFooterBand footer = groupBand.Footer; + RemoveReprint(footer); + await ShowBandAsync(footer, cancellationToken); + RemoveReprint(groupBand.Header); + } + + private async Task ShowGroupHeaderAsync(GroupHeaderBand header, CancellationToken cancellationToken) + { + header.AbsRowNo++; + header.RowNo++; + + if (header.ResetPageNumber && (header.FirstRowStartsNewPage || header.RowNo > 1)) + ResetLogicalPageNumber(); + if (header.KeepTogether) + StartKeep(header); + if (header.KeepWithData) + StartKeep(header.GroupDataBand); + + // start group event + OnStateChanged(header, EngineState.GroupStarted); + + await ShowBandAsync(header, cancellationToken); + if (header.RepeatOnEveryPage) + AddReprint(header); + + GroupFooterBand footer = header.GroupFooter; + if (footer != null) + { + if (footer.RepeatOnEveryPage) + AddReprint(footer); + } + } + + private async Task ShowGroupFooterAsync(GroupHeaderBand header, CancellationToken cancellationToken) + { + // finish group event + OnStateChanged(header, EngineState.GroupFinished); + + // rollback to previous data row to print the header condition in the footer. + DataBand dataBand = header.GroupDataBand; + DataSourceBase dataSource = dataBand.DataSource; + dataSource.Prior(); + + GroupFooterBand footer = header.GroupFooter; + if (footer != null) + { + footer.AbsRowNo++; + footer.RowNo++; + } + RemoveReprint(footer); + await ShowBandAsync(footer, cancellationToken); + RemoveReprint(header); + + // restore current row + dataSource.Next(); + + OutlineUp(header); + if (header.KeepTogether) + EndKeep(); + if (footer != null && footer.KeepWithData) + EndKeep(); + } + + + private async Task ShowGroupTreeAsync(GroupTreeItem root, CancellationToken cancellationToken) + { + if (root.Band != null) + { + root.Band.GroupDataBand.DataSource.CurrentRowNo = root.RowNo; + await ShowGroupHeaderAsync(root.Band, cancellationToken); + } + + if (root.Items.Count == 0) + { + if (root.RowCount != 0) + { + int rowCount = root.RowCount; + int maxRows = root.Band.GroupDataBand.MaxRows; + if (maxRows > 0 && rowCount > maxRows) + rowCount = maxRows; + bool keepFirstRow = NeedKeepFirstRow(root.Band); + bool keepLastRow = NeedKeepLastRow(root.Band.GroupDataBand); + await RunDataBandAsync(root.Band.GroupDataBand, rowCount, keepFirstRow, keepLastRow, cancellationToken); + } + } + else + { + await ShowDataHeaderAsync(root.FirstItem.Band, cancellationToken); + + for (int i = 0; i < root.Items.Count; i++) + { + GroupTreeItem item = root.Items[i]; + item.Band.IsFirstRow = i == 0; + item.Band.IsLastRow = i == root.Items.Count - 1; + + await ShowGroupTreeAsync(item, cancellationToken); + if (Report.Aborted) + break; + } + + await ShowDataFooterAsync(root.FirstItem.Band, cancellationToken); + } + + if (root.Band != null) + await ShowGroupFooterAsync(root.Band, cancellationToken); + } + + private async Task RunGroupAsync(GroupHeaderBand groupBand, CancellationToken cancellationToken) + { + DataSourceBase dataSource = groupBand.DataSource; + if (dataSource != null) + { + // init the datasource - set group conditions to sort data rows + groupBand.InitDataSource(); + + // show the group tree + await ShowGroupTreeAsync(MakeGroupTree(groupBand), cancellationToken); + + // finalize the datasource, remove the group condition + // from the databand sort + groupBand.FinalizeDataSource(); + + // do not leave the datasource in EOF state to allow print something in the footer + dataSource.Prior(); + } + } + + #endregion Private Methods + } +} diff --git a/FastReport.Base/Engine/ReportEngine.Groups.cs b/FastReport.Base/Engine/ReportEngine.Groups.cs index 84e7e3db..b1ca4612 100644 --- a/FastReport.Base/Engine/ReportEngine.Groups.cs +++ b/FastReport.Base/Engine/ReportEngine.Groups.cs @@ -11,8 +11,8 @@ private class GroupTreeItem { #region Fields - private GroupHeaderBand band; - private List items; + private readonly GroupHeaderBand band; + private readonly List items; private int rowNo; private int rowCount; @@ -164,7 +164,7 @@ private void ShowGroupFooter(GroupHeaderBand header) EndKeep(); } - private void InitGroupItem(GroupHeaderBand header, GroupTreeItem curItem) + private static void InitGroupItem(GroupHeaderBand header, GroupTreeItem curItem) { while (header != null) { @@ -179,7 +179,7 @@ private void InitGroupItem(GroupHeaderBand header, GroupTreeItem curItem) } } - private void CheckGroupItem(GroupHeaderBand header, GroupTreeItem curItem) + private static void CheckGroupItem(GroupHeaderBand header, GroupTreeItem curItem) { while (header != null) { diff --git a/FastReport.Base/Engine/ReportEngine.Pages.Async.cs b/FastReport.Base/Engine/ReportEngine.Pages.Async.cs new file mode 100644 index 00000000..f2d7a011 --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.Pages.Async.cs @@ -0,0 +1,133 @@ +using FastReport.Utils; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Engine +{ + public partial class ReportEngine + { + #region Private Methods + + private async Task RunReportPageAsync(ReportPage page, CancellationToken cancellationToken) + { + this.page = page; + InitReprint(); + pageNameForRecalc = null; + this.page.OnStartPage(EventArgs.Empty); + bool previousPage = await StartFirstPageAsync(cancellationToken); + OnStateChanged(this.page, EngineState.ReportPageStarted); + OnStateChanged(this.page, EngineState.PageStarted); + + DataBand keepSummaryBand = FindDeepmostDataBand(page); + if (keepSummaryBand != null) + keepSummaryBand.KeepSummary = true; + + if (this.page.IsManualBuild) + this.page.OnManualBuild(EventArgs.Empty); + else + await RunBandsAsync(page.Bands, cancellationToken); + + OnStateChanged(this.page, EngineState.PageFinished); + OnStateChanged(this.page, EngineState.ReportPageFinished); + EndLastPage(); // TODO + //recalculate unlimited + if (page.UnlimitedHeight || page.UnlimitedWidth) + { + PreparedPages.ModifyPageSize(page.Name); + if (previousPage && pageNameForRecalc != null) + PreparedPages.ModifyPageSize(pageNameForRecalc); + } + //recalculate unlimited + this.page.OnFinishPage(EventArgs.Empty); + + if (this.page.BackPage) + { + PreparedPages.InterleaveWithBackPage(PreparedPages.CurPage); + } + } + + private async Task RunReportPagesAsync(CancellationToken cancellationToken) + { +#if TIMETRIAL + if (new DateTime($YEAR, $MONTH, $DAY) < System.DateTime.Now) + throw new Exception("The trial version is now expired!"); +#endif + + for (int i = 0; i < Report.Pages.Count; i++) + { + ReportPage page = Report.Pages[i] as ReportPage; + + // Calc and apply visible expression if needed. + if (page != null && !String.IsNullOrEmpty(page.VisibleExpression)) + { + page.Visible = page.CalcVisibleExpression(page.VisibleExpression); + } + + if (page != null && page.Visible && page.Subreport == null) + await RunReportPageAsync(page, cancellationToken); + if (Report.Aborted) + break; + } + } + + private async Task RunBandsAsync(BandCollection bands, CancellationToken cancellationToken) + { + for (int i = 0; i < bands.Count; i++) + { + BandBase band = bands[i]; + if (band is DataBand) + await RunDataBandAsync(band as DataBand, cancellationToken); + else if (band is GroupHeaderBand) + await RunGroupAsync(band as GroupHeaderBand, cancellationToken); + if (Report.Aborted) + break; + } + } + + private async Task StartFirstPageAsync(CancellationToken cancellationToken) + { + var previousPage = StartFirstPageShared(); + + // show report title and page header + if (previousPage) + await ShowBandAsync(page.ReportTitle, cancellationToken); + else + { + if (page.Overlay != null) + await ShowBandAsync(page.Overlay, cancellationToken); + if (page.TitleBeforeHeader) + { + await ShowBandAsync(page.ReportTitle, cancellationToken); + ShowPageHeader(); + } + else + { + ShowPageHeader(); + await ShowBandAsync(page.ReportTitle, cancellationToken); + } + } + + // show column header + columnStartY = CurY; + await ShowBandAsync(page.ColumnHeader, cancellationToken); + + // calculate CurX before starting column event depending on Right to Left or Left to Right layout + if (Config.RightToLeft) + { + CurX = page.Columns.Positions[page.Columns.Positions.Count - 1] * Units.Millimeters; + } + else + { + CurX = page.Columns.Positions[0] * Units.Millimeters; + } + + // start column event + OnStateChanged(page, EngineState.ColumnStarted); + ShowProgress(); + return previousPage; + } + + #endregion Private Methods + } +} \ No newline at end of file diff --git a/FastReport.Base/Engine/ReportEngine.Pages.cs b/FastReport.Base/Engine/ReportEngine.Pages.cs index abb2918a..e88cbba3 100644 --- a/FastReport.Base/Engine/ReportEngine.Pages.cs +++ b/FastReport.Base/Engine/ReportEngine.Pages.cs @@ -123,6 +123,49 @@ private void ShowPageFooter(bool startPage) } private bool StartFirstPage() + { + var previousPage = StartFirstPageShared(); + + // show report title and page header + if (previousPage) + ShowBand(page.ReportTitle); + else + { + if (page.Overlay != null) + ShowBand(page.Overlay); + if (page.TitleBeforeHeader) + { + ShowBand(page.ReportTitle); + ShowPageHeader(); + } + else + { + ShowPageHeader(); + ShowBand(page.ReportTitle); + } + } + + // show column header + columnStartY = CurY; + ShowBand(page.ColumnHeader); + + // calculate CurX before starting column event depending on Right to Left or Left to Right layout + if (Config.RightToLeft) + { + CurX = page.Columns.Positions[page.Columns.Positions.Count - 1] * Units.Millimeters; + } + else + { + CurX = page.Columns.Positions[0] * Units.Millimeters; + } + + // start column event + OnStateChanged(page, EngineState.ColumnStarted); + ShowProgress(); + return previousPage; + } + + private bool StartFirstPageShared() { page.InitializeComponents(); @@ -221,42 +264,6 @@ private bool StartFirstPage() OutlineRoot(); AddPageOutline(); - // show report title and page header - if (previousPage) - ShowBand(page.ReportTitle); - else - { - if (page.Overlay != null) - ShowBand(page.Overlay); - if (page.TitleBeforeHeader) - { - ShowBand(page.ReportTitle); - ShowPageHeader(); - } - else - { - ShowPageHeader(); - ShowBand(page.ReportTitle); - } - } - - // show column header - columnStartY = CurY; - ShowBand(page.ColumnHeader); - - // calculate CurX before starting column event depending on Right to Left or Left to Right layout - if (Config.RightToLeft) - { - CurX = page.Columns.Positions[page.Columns.Positions.Count - 1] * Units.Millimeters; - } - else - { - CurX = page.Columns.Positions[0] * Units.Millimeters; - } - - // start column event - OnStateChanged(page, EngineState.ColumnStarted); - ShowProgress(); return previousPage; } diff --git a/FastReport.Base/Engine/ReportEngine.Subreports.Async.cs b/FastReport.Base/Engine/ReportEngine.Subreports.Async.cs new file mode 100644 index 00000000..a551ecba --- /dev/null +++ b/FastReport.Base/Engine/ReportEngine.Subreports.Async.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FastReport.Preview; + +namespace FastReport.Engine +{ + public partial class ReportEngine + { + #region Private Methods + + private Task RenderSubreportAsync(SubreportObject subreport, CancellationToken cancellationToken) + { + if (subreport.ReportPage != null) + return RunBandsAsync(subreport.ReportPage.Bands, cancellationToken); + return Task.CompletedTask; + } + + private async Task RenderOuterSubreportsAsync(BandBase parentBand, CancellationToken cancellationToken) + { + float saveCurY = CurY; + float saveOriginX = originX; + int saveCurPage = CurPage; + + float maxY = 0; + int maxPage = CurPage; + bool hasSubreports = false; + + try + { + for (int i = 0; i < parentBand.Objects.Count; i++) + { + SubreportObject subreport = parentBand.Objects[i] as SubreportObject; + + // Apply visible expression if needed. + if (subreport != null && !String.IsNullOrEmpty(subreport.VisibleExpression)) + { + subreport.Visible = subreport.CalcVisibleExpression(subreport.VisibleExpression); + } + + if (subreport != null && subreport.Visible && !subreport.PrintOnParent) + { + hasSubreports = true; + // restore start position + CurPage = saveCurPage; + CurY = saveCurY - subreport.Height; + originX = saveOriginX + subreport.Left; + // do not upload generated pages to the file cache + PreparedPages.CanUploadToCache = false; + + await RenderSubreportAsync(subreport, cancellationToken); + + // find maxY. We will continue from maxY when all subreports finished. + if (CurPage == maxPage) + { + if (CurY > maxY) + maxY = CurY; + } + else if (CurPage > maxPage) + { + maxPage = CurPage; + maxY = CurY; + } + } + } + } + finally + { + if (hasSubreports) + { + CurPage = maxPage; + CurY = maxY; + } + originX = saveOriginX; + PreparedPages.CanUploadToCache = true; + } + } + + #endregion Private Methods + } +} diff --git a/FastReport.Base/Export/ExportUtils.cs b/FastReport.Base/Export/ExportUtils.cs index 9d606340..4084a45b 100644 --- a/FastReport.Base/Export/ExportUtils.cs +++ b/FastReport.Base/Export/ExportUtils.cs @@ -223,25 +223,25 @@ internal static string GetExcelFormatSpecifier(FormatBase format, bool useLocale switch (f.NumberNegativePattern) { case 0: negative_pattern = "(" + fm_str + ")"; break; // (n) - case 1: negative_pattern = "-" + fm_str; break; // -n + // case 1: negative_pattern = "-" + fm_str; break; // -n case 2: negative_pattern = "- " + fm_str; break; // - n case 3: negative_pattern = fm_str + "-"; break; // n- case 4: negative_pattern = fm_str + " -"; break; // n - } - - return positive_pattern + ";" + negative_pattern; + string semicolon = !String.IsNullOrEmpty(negative_pattern) ? ";" : ""; + return positive_pattern + semicolon + negative_pattern; } else if (format is DateFormat) { - string parentalCase = CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "ru" ? "[$-FC19]" : ""; + string parentalCase = CultureInfo.CurrentCulture.TwoLetterISOLanguageName == "ru" ? "[$-419]" : ""; switch ((format as DateFormat).Format) { case "d": return DateTimeFormatInfo.CurrentInfo.ShortDatePattern + ";@"; - case "D": return "[$-F800]" + DateTimeFormatInfo.CurrentInfo.LongDatePattern.Replace("tt", "AM/PM") + ";@"; + case "D": return "[$-F800]" + "dddd, mmmm dd, yyyy"; case "f": return parentalCase + (DateTimeFormatInfo.CurrentInfo.LongDatePattern + " " + DateTimeFormatInfo.CurrentInfo.ShortTimePattern).Replace("tt", "AM/PM") + ";@"; case "F": return parentalCase + DateTimeFormatInfo.CurrentInfo.FullDateTimePattern.Replace("tt", "AM/PM") + ";@"; - case "MMMM yyyy": return (format as DateFormat).Format + ";@"; - default: return parentalCase + (format as DateFormat).Format.Replace("tt", "AM/PM") + ";@"; + case "MMMM yyyy": return parentalCase + DateTimeFormatInfo.CurrentInfo.YearMonthPattern + ";@"; + default: return (format as DateFormat).Format.Replace("tt", "AM/PM") + ";@"; } } else if (format is PercentFormat) diff --git a/FastReport.Base/Export/Html/HTMLExportDraw.cs b/FastReport.Base/Export/Html/HTMLExportDraw.cs index d76fd0a9..14118717 100644 --- a/FastReport.Base/Export/Html/HTMLExportDraw.cs +++ b/FastReport.Base/Export/Html/HTMLExportDraw.cs @@ -196,6 +196,7 @@ private void PrintPageStyle(FastString sb) { if (singlePage && pageBreaks) { + string paperProps = "size: portrait; "; sb.AppendLine(""); } } diff --git a/FastReport.Base/Export/Html/HTMLExportLayers.cs b/FastReport.Base/Export/Html/HTMLExportLayers.cs index 3bd64cc2..9af4906a 100644 --- a/FastReport.Base/Export/Html/HTMLExportLayers.cs +++ b/FastReport.Base/Export/Html/HTMLExportLayers.cs @@ -184,7 +184,7 @@ private FastString GetSpanText(TextObjectBase obj, FastString text, float ParagraphOffset) { FastString style = new FastString(); - style.Append("display:block;border:0;white-space: pre-wrap;width:").Append(Px(width * Zoom)); + style.Append("display:block;border:0;width:").Append(Px(width * Zoom)); if (ParagraphOffset != 0) style.Append("text-indent:").Append(Px(ParagraphOffset * Zoom)); if (obj.Padding.Left != 0) diff --git a/FastReport.Base/Functions/NumToWordsBase.cs b/FastReport.Base/Functions/NumToWordsBase.cs index c0a3a00a..c327bd7f 100644 --- a/FastReport.Base/Functions/NumToWordsBase.cs +++ b/FastReport.Base/Functions/NumToWordsBase.cs @@ -8,7 +8,7 @@ namespace FastReport.Functions internal abstract class NumToWordsBase { #region Private Methods - private string Str(decimal value, WordInfo senior, WordInfo junior) + private string Str(decimal value, WordInfo senior, WordInfo junior, bool dicimalPartToWord) { bool minus = false; if (value < 0) @@ -34,8 +34,20 @@ private string Str(decimal value, WordInfo senior, WordInfo junior) if (junior != null) { - r.Append(GetDecimalSeparator() + remainder.ToString("00 ")); - r.Append(Case(remainder, junior)); + string decimalPart; + + if (dicimalPartToWord) + { + decimalPart = Str(remainder, junior, null, false).ToLower(); + } + else + { + decimalPart = remainder.ToString("00 "); + } + + r.Append(GetDecimalSeparator() + decimalPart); + if (!dicimalPartToWord) + r.Append(Case(remainder, junior)); } r[0] = char.ToUpper(r[0]); @@ -160,12 +172,12 @@ protected virtual string Case(long value, WordInfo info) #endregion #region Public Methods - public string ConvertCurrency(decimal value, string currencyName) + public string ConvertCurrency(decimal value, string currencyName, bool decimalPartToWord) { try { CurrencyInfo currency = GetCurrency(currencyName); - return Str(value, currency.senior, currency.junior); + return Str(value, currency.senior, currency.junior, decimalPartToWord); } catch (KeyNotFoundException e) { @@ -181,18 +193,18 @@ public string ConvertCurrency(decimal value, string currencyName) } } - public string ConvertNumber(decimal value, bool male, string one, string two, string many) + public string ConvertNumber(decimal value, bool male, string one, string two, string many, bool decimalPartToWord) { - return Str(value, new WordInfo(male, one, two, many), null); + return Str(value, new WordInfo(male, one, two, many), null, decimalPartToWord); } public string ConvertNumber(decimal value, bool male, string seniorOne, string seniorTwo, string seniorMany, - string juniorOne, string juniorTwo, string juniorMany) + string juniorOne, string juniorTwo, string juniorMany, bool decimalPartToWord) { return Str(value, new WordInfo(male, seniorOne, seniorTwo, seniorMany), - new WordInfo(male, juniorOne, juniorTwo, juniorMany)); + new WordInfo(male, juniorOne, juniorTwo, juniorMany), decimalPartToWord); } #endregion } diff --git a/FastReport.Base/Functions/StdFunctions.cs b/FastReport.Base/Functions/StdFunctions.cs index 095ceee1..8ef3735c 100644 --- a/FastReport.Base/Functions/StdFunctions.cs +++ b/FastReport.Base/Functions/StdFunctions.cs @@ -713,6 +713,30 @@ public static string ToWords(object value) return ToWords(value, "USD"); } + /// + /// Converts a currency value to an english (US) string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWords(object value, bool decimalPartToWord) + { + return ToWords(value, "USD", decimalPartToWord); + } + + /// + /// Converts a currency value to an english (US) string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWords(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsEn().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); + } + /// /// Converts a currency value to an english (US) string representation of that value, /// using the specified currency. @@ -722,7 +746,20 @@ public static string ToWords(object value) /// The string representation of the specified value. public static string ToWords(object value, string currencyName) { - return new NumToWordsEn().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsEn().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a numeric value to an english (US) string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWords(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsEn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -734,7 +771,7 @@ public static string ToWords(object value, string currencyName) /// The string representation of the specified value. public static string ToWords(object value, string one, string many) { - return new NumToWordsEn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsEn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); } /// @@ -747,6 +784,17 @@ public static string ToWordsEnGb(object value) return ToWordsEnGb(value, "GBP"); } + /// + /// Converts a currency value to an english (GB) string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEnGb(object value, bool decimalPartToWord) + { + return ToWordsEnGb(value, "GBP", decimalPartToWord); + } + /// /// Converts a currency value to an english (GB) string representation of that value, /// using the specified currency. @@ -756,7 +804,20 @@ public static string ToWordsEnGb(object value) /// The string representation of the specified value. public static string ToWordsEnGb(object value, string currencyName) { - return new NumToWordsEnGb().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsEnGb().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to an english (GB) string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEnGb(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsEnGb().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -768,7 +829,20 @@ public static string ToWordsEnGb(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsEnGb(object value, string one, string many) { - return new NumToWordsEnGb().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsEnGb().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to an english (GB) string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEnGb(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsEnGb().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -781,6 +855,17 @@ public static string ToWordsEs(object value) return ToWordsEs(value, "EUR"); } + /// + /// Converts a currency value to a spanish string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEs(object value, bool decimalPartToWord) + { + return ToWordsEs(value, "EUR", decimalPartToWord); + } + /// /// Converts a currency value to a spanish string representation of that value, /// using the specified currency. @@ -790,7 +875,21 @@ public static string ToWordsEs(object value) /// The string representation of the specified value. public static string ToWordsEs(object value, string currencyName) { - return new NumToWordsEs().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsEs().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + + /// + /// Converts a currency value to a spanish string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEs(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsEs().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -802,7 +901,20 @@ public static string ToWordsEs(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsEs(object value, string one, string many) { - return new NumToWordsEs().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsEs().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a spanish string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsEs(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsEs().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -815,6 +927,17 @@ public static string ToWordsRu(object value) return ToWordsRu(value, "RUR"); } + /// + /// Converts a currency value to a russian string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsRu(object value, bool decimalPartToWord) + { + return ToWordsRu(value, "RUR", decimalPartToWord); + } + /// /// Converts a currency value to a russian string representation of that value, /// using the specified currency. @@ -824,7 +947,20 @@ public static string ToWordsRu(object value) /// The string representation of the specified value. public static string ToWordsRu(object value, string currencyName) { - return new NumToWordsRu().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsRu().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to a russian string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsRu(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsRu().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -838,7 +974,22 @@ public static string ToWordsRu(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsRu(object value, bool male, string one, string two, string many) { - return new NumToWordsRu().ConvertNumber(Convert.ToDecimal(value), male, one, two, many); + return new NumToWordsRu().ConvertNumber(Convert.ToDecimal(value), male, one, two, many, false); + } + + /// + /// Converts a numeric value to a russian string representation of that value. + /// + /// The numeric value to convert. + /// True if the name is of male gender. + /// The name in singular form, for example "страница". + /// The name in plural form, for example "страницы". + /// The name in plural form, for example "страниц". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsRu(object value, bool male, string one, string two, string many, bool decimalPartToWord) + { + return new NumToWordsRu().ConvertNumber(Convert.ToDecimal(value), male, one, two, many, decimalPartToWord); } /// @@ -851,6 +1002,17 @@ public static string ToWordsDe(object value) return ToWordsDe(value, "EUR"); } + /// + /// Converts a currency value to a german string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsDe(object value, bool decimalPartToWord) + { + return ToWordsDe(value, "EUR", decimalPartToWord); + } + /// /// Converts a currency value to a german string representation of that value, /// using the specified currency. @@ -860,7 +1022,20 @@ public static string ToWordsDe(object value) /// The string representation of the specified value. public static string ToWordsDe(object value, string currencyName) { - return new NumToWordsDe().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsDe().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to a german string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsDe(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsDe().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -872,7 +1047,20 @@ public static string ToWordsDe(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsDe(object value, string one, string many) { - return new NumToWordsDe().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsDe().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a german string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsDe(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsDe().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -885,6 +1073,17 @@ public static string ToWordsFr(object value) return ToWordsFr(value, "EUR"); } + /// + /// Converts a currency value to a french string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsFr(object value, bool decimalPartToWord) + { + return ToWordsFr(value, "EUR", decimalPartToWord); + } + /// /// Converts a currency value to a french string representation of that value, /// using the specified currency. @@ -894,7 +1093,20 @@ public static string ToWordsFr(object value) /// The string representation of the specified value. public static string ToWordsFr(object value, string currencyName) { - return new NumToWordsFr().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsFr().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to a french string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsFr(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsFr().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -906,7 +1118,20 @@ public static string ToWordsFr(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsFr(object value, string one, string many) { - return new NumToWordsFr().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsFr().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a french string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsFr(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsFr().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -919,6 +1144,17 @@ public static string ToWordsNl(object value) return ToWordsNl(value, "EUR"); } + /// + /// Converts a currency value to a dutch string representation of that value. + /// + /// The currency value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsNl(object value, bool decimalPartToWord) + { + return ToWordsNl(value, "EUR", decimalPartToWord); + } + /// /// Converts a currency value to a dutch string representation of that value, /// using the specified currency. @@ -928,7 +1164,20 @@ public static string ToWordsNl(object value) /// The string representation of the specified value. public static string ToWordsNl(object value, string currencyName) { - return new NumToWordsNl().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsNl().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to a dutch string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsNl(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsNl().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -940,7 +1189,20 @@ public static string ToWordsNl(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsNl(object value, string one, string many) { - return new NumToWordsNl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsNl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a dutch string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsNl(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsNl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -952,6 +1214,18 @@ public static string ToWordsIn(object value) { return ToWordsIn(value, "INR"); } + + /// + /// Converts a numeric value to a indian numbering system string representation of that value. + /// + /// the currency value to convert + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsIn(object value, bool decimalPartToWord) + { + return ToWordsIn(value, "INR", decimalPartToWord); + } + /// /// Converts a numeric value to a indian numbering system string representation of that value. /// @@ -960,7 +1234,19 @@ public static string ToWordsIn(object value) /// public static string ToWordsIn(object value, string currencyName) { - return new NumToWordsIn().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsIn().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a numeric value to a indian numbering system string representation of that value. + /// + /// he numeric value to convert. + /// The 3-digit ISO name of the currency, for example "INR". + /// Flag indicating that decimal part should be converted to words. + /// + public static string ToWordsIn(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsIn().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -972,7 +1258,20 @@ public static string ToWordsIn(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsIn(object value, string one, string many) { - return new NumToWordsIn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsIn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a indian numbering system string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "page". + /// The name in plural form, for example "pages". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsIn(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsIn().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -985,6 +1284,16 @@ public static string ToWordsUkr(object value) return ToWordsUkr(value, "UAH"); } + /// + /// Converts a numeric value to a ukrainian string representation of that value. + /// + /// The numeric value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsUkr(object value, bool decimalPartToWord) + { + return ToWordsUkr(value, "UAH", decimalPartToWord); + } /// /// Converts a currency value to a ukrainian string representation of that value, @@ -995,7 +1304,20 @@ public static string ToWordsUkr(object value) /// The string representation of the specified value. public static string ToWordsUkr(object value, string currencyName) { - return new NumToWordsUkr().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsUkr().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a currency value to a ukrainian string representation of that value, + /// using the specified currency. + /// + /// The currency value to convert. + /// The 3-digit ISO name of the currency, for example "UAH". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsUkr(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsUkr().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -1009,9 +1331,23 @@ public static string ToWordsUkr(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsUkr(object value, bool male, string one, string two, string many) { - return new NumToWordsUkr().ConvertNumber(Convert.ToDecimal(value), male, one, two, many); + return new NumToWordsUkr().ConvertNumber(Convert.ToDecimal(value), male, one, two, many, false); } + /// + /// Converts a numeric value to a ukrainian string representation of that value. + /// + /// The numeric value to convert. + /// True if the name is of male gender. + /// The name in singular form, for example "сторінка". + /// The name in plural form, for example "сторінки". + /// The name in plural form, for example "сторінок". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsUkr(object value, bool male, string one, string two, string many, bool decimalPartToWord) + { + return new NumToWordsUkr().ConvertNumber(Convert.ToDecimal(value), male, one, two, many, decimalPartToWord); + } /// /// Converts a numeric value to a spanish string representation of that value. @@ -1023,6 +1359,17 @@ public static string ToWordsSp(object value) return ToWordsSp(value, "EUR"); } + /// + /// Converts a numeric value to a spanish string representation of that value. + /// + /// The numeric value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsSp(object value, bool decimalPartToWord) + { + return ToWordsSp(value, "EUR", decimalPartToWord); + } + /// /// Converts a numeric value to a spanish representation of that value. /// @@ -1031,7 +1378,19 @@ public static string ToWordsSp(object value) /// public static string ToWordsSp(object value, string currencyName) { - return new NumToWordsSp().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsSp().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a numeric value to a spanish representation of that value. + /// + /// he numeric value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// + public static string ToWordsSp(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsSp().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -1043,7 +1402,20 @@ public static string ToWordsSp(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsSp(object value, string one, string many) { - return new NumToWordsSp().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsSp().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a spanish string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "silla". + /// The name in plural form, for example "Sillas". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsSp(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsSp().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -1056,6 +1428,17 @@ public static string ToWordsPersian(object value) return ToWordsPersian(value, "EUR"); } + /// + /// Converts a numeric value to a persian string representation of that value. + /// + /// The numeric value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsPersian(object value, bool decimalPartToWord) + { + return ToWordsPersian(value, "EUR", decimalPartToWord); + } + /// /// Converts a numeric value to a persian representation of that value. /// @@ -1064,7 +1447,19 @@ public static string ToWordsPersian(object value) /// public static string ToWordsPersian(object value, string currencyName) { - return new NumToWordsPersian().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsPersian().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a numeric value to a persian representation of that value. + /// + /// he numeric value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// + public static string ToWordsPersian(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsPersian().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -1076,7 +1471,20 @@ public static string ToWordsPersian(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsPersian(object value, string one, string many) { - return new NumToWordsPersian().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsPersian().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a persian string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "silla". + /// The name in plural form, for example "Sillas". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsPersian(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsPersian().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -1089,6 +1497,17 @@ public static string ToWordsPl(object value) return ToWordsPl(value, "PLN"); } + /// + /// Converts a numeric value to a polish string representation of that value. + /// + /// The numeric value to convert. + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsPl(object value, bool decimalPartToWord) + { + return ToWordsPl(value, "PLN", decimalPartToWord); + } + /// /// Converts a numeric value to a polish representation of that value. /// @@ -1097,7 +1516,19 @@ public static string ToWordsPl(object value) /// public static string ToWordsPl(object value, string currencyName) { - return new NumToWordsPl().ConvertCurrency(Convert.ToDecimal(value), currencyName); + return new NumToWordsPl().ConvertCurrency(Convert.ToDecimal(value), currencyName, false); + } + + /// + /// Converts a numeric value to a polish representation of that value. + /// + /// he numeric value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// Flag indicating that decimal part should be converted to words. + /// + public static string ToWordsPl(object value, string currencyName, bool decimalPartToWord) + { + return new NumToWordsPl().ConvertCurrency(Convert.ToDecimal(value), currencyName, decimalPartToWord); } /// @@ -1109,7 +1540,20 @@ public static string ToWordsPl(object value, string currencyName) /// The string representation of the specified value. public static string ToWordsPl(object value, string one, string many) { - return new NumToWordsPl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + return new NumToWordsPl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, false); + } + + /// + /// Converts a numeric value to a polish string representation of that value. + /// + /// The numeric value to convert. + /// The name in singular form, for example "silla". + /// The name in plural form, for example "Sillas". + /// Flag indicating that decimal part should be converted to words. + /// The string representation of the specified value. + public static string ToWordsPl(object value, string one, string many, bool decimalPartToWord) + { + return new NumToWordsPl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many, decimalPartToWord); } /// @@ -1363,45 +1807,81 @@ internal static void Register() RegisteredObjects.InternalAddFunction(stdConv.GetMethod("ToSingle", new Type[] { typeof(object) }), "Conversion"); RegisteredObjects.InternalAddFunction(stdConv.GetMethod("ToString", new Type[] { typeof(object) }), "Conversion"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object) }), "Conversion,ToWords"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWords"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWords"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWords"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWords"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWords", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWords"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object) }), "Conversion,ToWordsIn"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsIn"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsIn"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsIn"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsIn"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsIn", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsIn"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object) }), "Conversion,ToWordsDe"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsDe"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsDe"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsDe"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsDe"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsDe", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsDe"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object) }), "Conversion,ToWordsEnGb"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsEnGb"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsEnGb"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsEnGb"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsEnGb"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEnGb", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsEnGb"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object) }), "Conversion,ToWordsEs"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsEs"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsEs"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsEs"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsEs"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsEs", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsEs"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object) }), "Conversion,ToWordsFr"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsFr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsFr"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsFr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsFr"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsFr", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsFr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object) }), "Conversion,ToWordsNl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsNl"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsNl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsNl"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsNl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsNl", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsNl"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object) }), "Conversion,ToWordsRu"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsRu"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsRu"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsRu"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object), typeof(bool), typeof(string), typeof(string), typeof(string) }), "Conversion,ToWordsRu"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsRu", new Type[] { typeof(object), typeof(bool), typeof(string), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsRu"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object) }), "Conversion,ToWordsUkr"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsUkr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsUkr"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsUkr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object), typeof(bool), typeof(string), typeof(string), typeof(string) }), "Conversion,ToWordsUkr"); - RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsSp"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsUkr", new Type[] { typeof(object), typeof(bool), typeof(string), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsUkr"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object) }), "Conversion,ToWordsSp"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsSp"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsSp"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsSp"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsSp"); - RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsSp", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsSp"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsPersian"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPersian", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsPersian"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object) }), "Conversion,ToWordsPl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToWordsPl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsPl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string), typeof(bool) }), "Conversion,ToWordsPl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsPl"); + RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string), typeof(string), typeof(bool) }), "Conversion,ToWordsPl"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToLetters", new Type[] { typeof(object) }), "Conversion,ToLetters"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToLetters", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToLetters"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToLettersRu", new Type[] { typeof(object) }), "Conversion,ToLettersRu"); RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToLettersRu", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToLettersRu"); - RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsPl"); - RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object) }), "Conversion,ToWordsPl"); - RegisteredObjects.InternalAddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsPl"); #endregion #region Program Flow diff --git a/FastReport.Base/Gauge/GaugeObject.Async.cs b/FastReport.Base/Gauge/GaugeObject.Async.cs new file mode 100644 index 00000000..3120ecd2 --- /dev/null +++ b/FastReport.Base/Gauge/GaugeObject.Async.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport.Gauge +{ + public partial class GaugeObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion // Report Engine + } +} diff --git a/FastReport.Base/Gauge/GaugeObject.cs b/FastReport.Base/Gauge/GaugeObject.cs index c1047d52..a53d8993 100644 --- a/FastReport.Base/Gauge/GaugeObject.cs +++ b/FastReport.Base/Gauge/GaugeObject.cs @@ -221,7 +221,11 @@ public override string[] GetExpressions() public override void GetData() { base.GetData(); + GetDataShared(); + } + private void GetDataShared() + { if (!String.IsNullOrEmpty(Expression)) { object val = Report.Calc(Expression); diff --git a/FastReport.Base/HtmlObject.Async.cs b/FastReport.Base/HtmlObject.Async.cs new file mode 100644 index 00000000..cfd256db --- /dev/null +++ b/FastReport.Base/HtmlObject.Async.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public partial class HtmlObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion + } +} \ No newline at end of file diff --git a/FastReport.Base/HtmlObject.cs b/FastReport.Base/HtmlObject.cs index 1b4faea9..f7f830f1 100644 --- a/FastReport.Base/HtmlObject.cs +++ b/FastReport.Base/HtmlObject.cs @@ -205,7 +205,11 @@ public override float CalcHeight() public override void GetData() { base.GetData(); + GetDataShared(); + } + private void GetDataShared() + { // process expressions if (AllowExpressions) { diff --git a/FastReport.Base/IContainDataSource.cs b/FastReport.Base/IContainDataSource.cs new file mode 100644 index 00000000..9cd50f3a --- /dev/null +++ b/FastReport.Base/IContainDataSource.cs @@ -0,0 +1,9 @@ +using FastReport.Data; + +namespace FastReport +{ + internal interface IContainDataSource + { + void UpdateDataSourceRef(DataSourceBase newRefDatasource); + } +} diff --git a/FastReport.Base/Matrix/MatrixObject.Async.cs b/FastReport.Base/Matrix/MatrixObject.Async.cs new file mode 100644 index 00000000..c5594908 --- /dev/null +++ b/FastReport.Base/Matrix/MatrixObject.Async.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport.Matrix +{ + public partial class MatrixObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion + } +} \ No newline at end of file diff --git a/FastReport.Base/Matrix/MatrixObject.cs b/FastReport.Base/Matrix/MatrixObject.cs index e56c3687..dbe59199 100644 --- a/FastReport.Base/Matrix/MatrixObject.cs +++ b/FastReport.Base/Matrix/MatrixObject.cs @@ -91,7 +91,7 @@ public enum MatrixEvenStylePriority /// matrix.Data.Rows[0].TemplateTotalCell.Text = "Grand Total"; /// /// - public partial class MatrixObject : TableBase + public partial class MatrixObject : TableBase, IContainDataSource { #region Fields private bool autoSize; @@ -616,6 +616,14 @@ private void dataBand_BeforePrint(object sender, EventArgs e) if (match is bool && (bool)match == true) Helper.AddDataRow(); } + + void IContainDataSource.UpdateDataSourceRef(DataSourceBase newRefDatasource) + { + if (newRefDatasource != null && (newRefDatasource.Name == DataSource.Name || newRefDatasource == DataSource)) + { + DataSource = newRefDatasource; + } + } #endregion #region Protected Methods @@ -774,7 +782,11 @@ public override void SaveState() public override void GetData() { base.GetData(); + GetDataShared(); + } + private void GetDataShared() + { if (!IsOnFooter) { Helper.StartPrint(); diff --git a/FastReport.Base/PictureObject.Async.cs b/FastReport.Base/PictureObject.Async.cs new file mode 100644 index 00000000..f1fc8eee --- /dev/null +++ b/FastReport.Base/PictureObject.Async.cs @@ -0,0 +1,73 @@ +using System; +using System.Drawing; +using FastReport.Utils; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public partial class PictureObject + { + #region Public Methods + + public override async Task LoadImageAsync(CancellationToken cancellationToken) + { + if (!String.IsNullOrEmpty(ImageLocation)) + { + try + { + Uri uri = CalculateUri(); + byte[] bytes; + if (uri.IsFile) + bytes = await ImageHelper.LoadAsync(uri.LocalPath, cancellationToken); + else + bytes = await ImageHelper.LoadURLAsync(uri, cancellationToken); + SetImageData(bytes); + } + catch + { + Image = null; + } + + ShouldDisposeImage = true; + } + } + +#endregion + + #region Report Engine + + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + + if (!String.IsNullOrEmpty(DataColumn)) + { + // reset the image + Image = null; + imageData = null; + + object data = Report.GetColumnValueNullable(DataColumn); + if (data is byte[]) + { + SetImageData((byte[])data); + } + else if (data is Image) + { + Image = data as Image; + } + else if (data is string dataStr) + { + await SetImageLocationAsync(dataStr, true, cancellationToken); + } + } + else + { + // no other data received + await UpdateImageLocationAsync(cancellationToken); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/FastReport.Base/PictureObject.cs b/FastReport.Base/PictureObject.cs index 5faf3517..31d500e4 100644 --- a/FastReport.Base/PictureObject.cs +++ b/FastReport.Base/PictureObject.cs @@ -666,7 +666,7 @@ public void EstablishImageForm(GraphicsPath path, float drawLeft, float drawTop, break; } } - #endregion +#endregion #region Report Engine @@ -691,6 +691,7 @@ public override void FinalizeComponent() public override void GetData() { base.GetData(); + if (!String.IsNullOrEmpty(DataColumn)) { // reset the image @@ -701,16 +702,23 @@ public override void GetData() if (data is byte[]) { SetImageData((byte[])data); + return; } else if (data is Image) { Image = data as Image; + return; } - else if (data is string) + else if (data is string dataStr) { - ImageLocation = data.ToString(); + SetImageLocation(dataStr, true); } } + else + { + // no other data received + UpdateImageLocation(); + } } /// diff --git a/FastReport.Base/PictureObjectBase.Async.cs b/FastReport.Base/PictureObjectBase.Async.cs new file mode 100644 index 00000000..f9c3f021 --- /dev/null +++ b/FastReport.Base/PictureObjectBase.Async.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using FastReport.Utils; + +namespace FastReport +{ + public abstract partial class PictureObjectBase + { + internal async Task SetImageLocationAsync(string value, bool forceUpdate, CancellationToken cancellationToken) + { + if (!String.IsNullOrEmpty(Config.ReportSettings.ImageLocationRoot)) + imageLocation = value.Replace(Config.ReportSettings.ImageLocationRoot, ""); + else + imageLocation = value; + + if (forceUpdate) + await UpdateImageLocationAsync(cancellationToken); + } + + internal async Task UpdateImageLocationAsync(CancellationToken cancellationToken) + { + await LoadImageAsync(cancellationToken); + ResetImageIndex(); + } + + #region Public Methods + + /// + /// Loads image asynchronously + /// + /// + /// You mustn't call this method when override it in nested class because it will call synchronous implementation + /// + public virtual Task LoadImageAsync(CancellationToken cancellationToken) + { + // fallback if this method wasn't overridden + LoadImage(); + return Task.CompletedTask; + } + + #endregion + } +} \ No newline at end of file diff --git a/FastReport.Base/PictureObjectBase.cs b/FastReport.Base/PictureObjectBase.cs index a9a96294..3c26c021 100644 --- a/FastReport.Base/PictureObjectBase.cs +++ b/FastReport.Base/PictureObjectBase.cs @@ -158,12 +158,7 @@ public string ImageLocation get { return imageLocation; } set { - if (!String.IsNullOrEmpty(Config.ReportSettings.ImageLocationRoot)) - imageLocation = value.Replace(Config.ReportSettings.ImageLocationRoot, ""); - else - imageLocation = value; - LoadImage(); - ResetImageIndex(); + SetImageLocation(value, true); // or IsDesigning } } @@ -898,5 +893,22 @@ public override string[] GetExpressions() return expressions.ToArray(); } + + internal void SetImageLocation(string value, bool forceUpdate) + { + if (!String.IsNullOrEmpty(Config.ReportSettings.ImageLocationRoot)) + imageLocation = value.Replace(Config.ReportSettings.ImageLocationRoot, ""); + else + imageLocation = value; + + if (forceUpdate) + UpdateImageLocation(); + } + + internal void UpdateImageLocation() + { + LoadImage(); + ResetImageIndex(); + } } } \ No newline at end of file diff --git a/FastReport.Base/RFIDLabel.Async.cs b/FastReport.Base/RFIDLabel.Async.cs new file mode 100644 index 00000000..4374925b --- /dev/null +++ b/FastReport.Base/RFIDLabel.Async.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public partial class RFIDLabel + { + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + } +} diff --git a/FastReport.Base/RFIDLabel.cs b/FastReport.Base/RFIDLabel.cs index 1ddf32ee..42943a8d 100644 --- a/FastReport.Base/RFIDLabel.cs +++ b/FastReport.Base/RFIDLabel.cs @@ -401,6 +401,11 @@ public bool AdaptiveAntenna public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (TIDBank.DataColumn.Contains("[") && TIDBank.DataColumn.Contains("]")) TIDBank.Data = Report.Calc(TIDBank.DataColumn).ToString(); diff --git a/FastReport.Base/Report.Async.cs b/FastReport.Base/Report.Async.cs new file mode 100644 index 00000000..7c26a79e --- /dev/null +++ b/FastReport.Base/Report.Async.cs @@ -0,0 +1,117 @@ +using FastReport.Code; +using FastReport.Engine; +using FastReport.Utils; + +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport +{ + public partial class Report + { + + #region Script related + + internal async Task CompileAsync(CancellationToken token) + { + FillDataSourceCache(); + +#if REFLECTION_EMIT_COMPILER + if (Config.CompilerSettings.ReflectionEmitCompiler) + { + SetIsCompileNeeded(); + if (!IsCompileNeeded) + return; + } +#endif + + if (needCompile) + { + AssemblyDescriptor descriptor = new AssemblyDescriptor(this, ScriptText); + assemblies.Clear(); + assemblies.Add(descriptor); + descriptor.AddObjects(); + descriptor.AddExpressions(); + descriptor.AddFunctions(); + await descriptor.CompileAsync(token); + } + else + { + InternalInit(); + } + } + + /// + /// Prepares the report asynchronously. + /// + /// Cancellation token + /// true if report was prepared successfully. + public Task PrepareAsync(CancellationToken token = default) + { + return PrepareAsync(false, token); + } + + public Task PrepareAsync(bool append, CancellationToken token = default) + { + return PrepareAsync(append, true, token); + } + + public async Task PrepareAsync(bool append, bool resetDataState, CancellationToken token = default) + { + SetRunning(true); + try + { + if (PreparedPages == null || !append) + { + ClearPreparedPages(); + + SetPreparedPages(new Preview.PreparedPages(this)); + } + engine = new ReportEngine(this); + + if (!Config.WebMode) + StartPerformanceCounter(); + + try + { + await CompileAsync(token).ConfigureAwait(false); + isParameterChanged = false; + return await Engine.RunAsync(true, append, resetDataState, token); + } + finally + { + if (!Config.WebMode) + StopPerformanceCounter(); + } + } + finally + { + SetRunning(false); + } + } + + + /// + /// For internal use only. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public async Task PreparePhase1Async(CancellationToken cancellationToken) + { + bool webDialog = false; + SetRunning(true); + if (preparedPages != null) + { + // if prepared pages are set before => it's call method again => it's web dialog + webDialog = true; + preparedPages.Clear(); + } + SetPreparedPages(new Preview.PreparedPages(this)); + engine = new ReportEngine(this); + await CompileAsync(cancellationToken); + Engine.RunPhase1(true, webDialog); + } + + #endregion Public Methods + } +} \ No newline at end of file diff --git a/FastReport.Base/Report.cs b/FastReport.Base/Report.cs index 4a4ef574..41dd8499 100644 --- a/FastReport.Base/Report.cs +++ b/FastReport.Base/Report.cs @@ -18,8 +18,6 @@ using System.Linq.Expressions; using System.Security; using System.Text; -using System.Threading; -using System.Threading.Tasks; using System.Windows.Forms; namespace FastReport @@ -909,6 +907,21 @@ internal CodeHelperBase CodeHelper get { return codeHelper; } } + internal bool HasPageLinks + { + get + { + foreach(var page in Pages) + { + if(page is ReportPage reportPage && reportPage.LinkToPage.IsInherit) + return true; + } + + return false; + } + } + + public IGraphics MeasureGraphics { get @@ -1306,36 +1319,6 @@ internal void Compile() } } -#if ASYNC - internal async Task CompileAsync(CancellationToken token) - { - FillDataSourceCache(); - -#if REFLECTION_EMIT_COMPILER - if (Config.CompilerSettings.ReflectionEmitCompiler) - { - SetIsCompileNeeded(); - if (!IsCompileNeeded) - return; - } -#endif - - if (needCompile) - { - AssemblyDescriptor descriptor = new AssemblyDescriptor(this, ScriptText); - assemblies.Clear(); - assemblies.Add(descriptor); - descriptor.AddObjects(); - descriptor.AddExpressions(); - descriptor.AddFunctions(); - await descriptor.CompileAsync(token); - } - else - { - InternalInit(); - } - } -#endif /// /// Initializes the report's fields. @@ -1643,6 +1626,10 @@ public object GetParameterValue(string complexName) { return (double)(int)par.Value; } + if (par.Value.GetType() != par.DataType) + { + return ConvertToColumnDataType(par.Value, par.DataType, Report.ConvertNulls); + } return par.Value; } return null; @@ -2000,6 +1987,42 @@ public void Save(Stream stream) } } + /// + /// Saves the report to a stream. + /// + /// The stream to save to. + /// Enables saving linked pages to original files. + public void Save(Stream stream, bool savePageLinks) + { + using (FRWriter writer = new FRWriter()) + { + writer.SaveExternalPages = savePageLinks; + + if (IsAncestor) + writer.GetDiff += new DiffEventHandler(GetDiff); + writer.Write(this); + + List disposeList = new List(); + + if (Compressed) + { + stream = Compressor.Compress(stream); + disposeList.Add(stream); + } + if (!String.IsNullOrEmpty(Password)) + { + stream = Crypter.Encrypt(stream, Password); + disposeList.Insert(0, stream); + } + writer.Save(stream); + + foreach (Stream s in disposeList) + { + s.Dispose(); + } + } + } + /// /// Saves the report to a file. /// @@ -2013,6 +2036,20 @@ public void Save(string fileName) } } + /// + /// Saves the report to a file. + /// + /// The name of the file to save to. + /// Enables saving linked pages to original files. + public void Save(string fileName, bool savePageLinks) + { + FileName = fileName; + using (FileStream f = new FileStream(fileName, FileMode.Create)) + { + Save(f, savePageLinks); + } + } + /// /// Saves the report to a stream with randomized values in data sources. /// @@ -2162,7 +2199,29 @@ public string SaveToString() { using (MemoryStream stream = new MemoryStream()) { - Save(stream); + Save(stream, false); + + if (Compressed || !String.IsNullOrEmpty(Password)) + { + return Convert.ToBase64String(stream.ToArray()); + } + else + { + return Encoding.UTF8.GetString(stream.ToArray()); + } + } + } + + /// + /// Saves the report to a string. + /// + /// The string that contains a stream. + /// Enables saving linked pages to original files. + public string SaveToString(bool savePageLinks) + { + using (MemoryStream stream = new MemoryStream()) + { + Save(stream, savePageLinks); if (Compressed || !String.IsNullOrEmpty(Password)) { @@ -2461,60 +2520,38 @@ public bool Prepare() return Prepare(false); } -#if ASYNC + /// - /// Prepares the report asynchronously. + /// Prepares the report. /// - /// Cancellation token - /// true if report was prepared succesfully. - [EditorBrowsable(EditorBrowsableState.Never)] // TODO - public Task PrepareAsync(CancellationToken token = default) - { - return PrepareAsync(false, token); - } - - private async Task PrepareAsync(bool append, CancellationToken token = default) + /// Specifies whether the new report should be added to a + /// report that was prepared before. + /// true if report was prepared successfully. + /// + /// Use this method to merge prepared reports. + /// + /// This example shows how to merge two reports and preview the result: + /// + /// Report report = new Report(); + /// report.Load("report1.frx"); + /// report.Prepare(); + /// report.Load("report2.frx"); + /// report.Prepare(true); + /// report.ShowPrepared(); + /// + /// + public bool Prepare(bool append) { - SetRunning(true); - try - { - if (PreparedPages == null || !append) - { - ClearPreparedPages(); - - SetPreparedPages(new Preview.PreparedPages(this)); - } - engine = new ReportEngine(this); - - if (!Config.WebMode) - StartPerformanceCounter(); - - try - { - await CompileAsync(token).ConfigureAwait(false); - isParameterChanged = false; - return Engine.Run(true, append, true); - } - finally - { - if (!Config.WebMode) - StopPerformanceCounter(); - } - } - finally - { - SetRunning(false); - } + return Prepare(append, true); } -#endif - /// /// Prepares the report. /// /// Specifies whether the new report should be added to a /// report that was prepared before. - /// true if report was prepared succesfully. + /// Specifies whether the reset data state. + /// true if report was prepared successfully. /// /// Use this method to merge prepared reports. /// @@ -2528,7 +2565,7 @@ private async Task PrepareAsync(bool append, CancellationToken token = def /// report.ShowPrepared(); /// /// - public bool Prepare(bool append) + public bool Prepare(bool append, bool resetDataState) { SetRunning(true); try @@ -2548,7 +2585,7 @@ public bool Prepare(bool append) { Compile(); isParameterChanged = false; - return Engine.Run(true, append, true); + return Engine.Run(true, append, resetDataState); } finally { diff --git a/FastReport.Base/ReportComponentBase.Async.cs b/FastReport.Base/ReportComponentBase.Async.cs new file mode 100644 index 00000000..055cf2eb --- /dev/null +++ b/FastReport.Base/ReportComponentBase.Async.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using System.Threading; + +namespace FastReport +{ + public abstract partial class ReportComponentBase + { + public virtual Task GetDataAsync(CancellationToken cancellationToken) + { + GetDataShared(); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/FastReport.Base/ReportComponentBase.cs b/FastReport.Base/ReportComponentBase.cs index 0564a123..be776524 100644 --- a/FastReport.Base/ReportComponentBase.cs +++ b/FastReport.Base/ReportComponentBase.cs @@ -1004,6 +1004,11 @@ public virtual float CalcHeight() /// In this method you should get the data from a datasource that the object is connected to. /// public virtual void GetData() + { + GetDataShared(); + } + + private void GetDataShared() { Hyperlink.Calculate(); diff --git a/FastReport.Base/ReportPage.cs b/FastReport.Base/ReportPage.cs index ce0181e9..e3327757 100644 --- a/FastReport.Base/ReportPage.cs +++ b/FastReport.Base/ReportPage.cs @@ -5,6 +5,9 @@ using FastReport.Utils; using System.Drawing.Design; using System.Drawing.Printing; +using System.IO; +using System.Xml; +using FastReport.Data; namespace FastReport { @@ -47,6 +50,128 @@ namespace FastReport /// public partial class ReportPage : PageBase, IParent { + [TypeConverter(typeof(FastReport.TypeConverters.FRExpandableObjectConverter))] + public class PageLink + { + string reportPath; + string pageName; + bool saveNames; + bool isInherit; + ReportPage page; + + /// + /// Get or set path to report file. + /// + public string ReportPath + { + get + { + return reportPath; + } + set + { + reportPath = value; + if (value != reportPath && !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(pageName)) + { + page.LoadExternalPage(this, page.Report, page.Name); + } + } + } + + [Browsable(false)] + public bool IsInherit + { + get + { + return isInherit; + } + internal set + { + isInherit = value; + } + } + + + /// + /// Get or set name of linked page. + /// + public string PageName + { + get + { + return pageName; + } + set + { + pageName = value; + if (value != pageName && !string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(reportPath)) + { + page.LoadExternalPage(this, page.Report, page.Name); + } + } + } + + + /// + /// Gets or sets a value indicating whether need save original name of objects. + /// + public bool SaveNames + { + get + { + return saveNames; + } + set + { + if (value != saveNames) + { + saveNames = value; + page.LoadExternalPage(this, page.Report, page.Name); + } + } + } + + public void Deserialize(FRReader reader, string prefix) + { + reportPath = reader.ReadStr(prefix + ".ReportPath"); + pageName = reader.ReadStr(prefix + ".PageName"); + saveNames = reader.ReadBool(prefix + ".SaveName"); + isInherit = reader.ReadBool(prefix + ".IsInherit"); + } + + /// + public void Serialize(FRWriter writer, string prefix, PageLink c) + { + if (ReportPath != c.ReportPath) + writer.WriteStr(prefix + ".ReportPath", ReportPath); + if (PageName != c.PageName) + writer.WriteStr(prefix + ".PageName", PageName); + if (SaveNames != c.SaveNames) + writer.WriteBool(prefix + ".SaveName", SaveNames); + if (IsInherit != c.IsInherit) + writer.WriteBool(prefix + ".IsInherit", IsInherit); + } + + internal PageLink Clone() + { + var result = new PageLink(page); + result.reportPath = ReportPath; + result.pageName = PageName; + result.saveNames = SaveNames; + result.IsInherit = IsInherit; + return result; + } + + public PageLink(ReportPage page) + { + saveNames = false; + reportPath = ""; + pageName = ""; + isInherit = false; + this.page = page; + } + } + #region Constants private const float MAX_PAPER_SIZE_MM = 2000000000; @@ -93,6 +218,7 @@ public partial class ReportPage : PageBase, IParent private int otherPagesSource; private int lastPageSource; private Duplex duplex; + private PageLink pageLink; private bool unlimitedHeight; private bool printOnRollPaper; @@ -784,6 +910,15 @@ internal bool IsManualBuild { get { return !String.IsNullOrEmpty(manualBuildEvent) || ManualBuild != null; } } + + /// + /// Get or set a link to the page. + /// + public PageLink LinkToPage + { + get { return pageLink; } + set { pageLink = value; } + } #endregion #region Private Methods @@ -982,13 +1117,21 @@ public override void Assign(Base source) UnlimitedWidth = src.UnlimitedWidth; UnlimitedHeightValue = src.UnlimitedHeightValue; UnlimitedWidthValue = src.UnlimitedWidthValue; + LinkToPage = src.LinkToPage.Clone(); } /// public override void Serialize(FRWriter writer) { ReportPage c = writer.DiffObject as ReportPage; + bool saveChild = writer.SaveChildren; + if (!string.IsNullOrEmpty(this.LinkToPage.ReportPath) && writer.SaveExternalPages) + { + if (writer.SaveChildren && writer.SerializeTo == SerializeTo.Report) + writer.SaveChildren = false; + } base.Serialize(writer); + if (ExportAlias != c.ExportAlias) writer.WriteStr("ExportAlias", ExportAlias); if (Landscape != c.Landscape) @@ -1055,6 +1198,102 @@ public override void Serialize(FRWriter writer) writer.WriteFloat("OtherPageSource", OtherPagesSource); if (Duplex.ToString() != c.Duplex.ToString()) writer.WriteStr("Duplex", Duplex.ToString()); + if (writer.SerializeTo != SerializeTo.SourcePages) + LinkToPage.Serialize(writer, nameof(LinkToPage), c.LinkToPage); + + if (writer.SerializeTo == SerializeTo.Report && writer.SaveExternalPages && !string.IsNullOrEmpty(LinkToPage.PageName) + && !string.IsNullOrEmpty(LinkToPage.ReportPath) && File.Exists(LinkToPage.ReportPath)) + { + using (Report report = new Report()) + { + report.Load(LinkToPage.ReportPath); + foreach (PageBase item in report.Pages) + { + if (item is ReportPage page && item.Name == LinkToPage.PageName) + { + var temp = page.LinkToPage; + bool isAncestor = page.IsAncestor; + page.AssignAll(this, true, true); + page.SetAncestor(isAncestor); + page.SetReport(report); + page.Parent = report; + page.LinkToPage = temp; + if (!LinkToPage.SaveNames) + { + if (!string.IsNullOrEmpty(page.Alias)) + page.Name = page.Alias; + foreach (Base obj in page.AllObjects) + { + if (!string.IsNullOrEmpty(obj.Alias)) + { + obj.SetReport(report); + obj.SetName(obj.Alias); + } + } + } + break; + } + } + report.Save(LinkToPage.ReportPath); + } + } + writer.SaveChildren = saveChild; + } + + /// + public override void Deserialize(FRReader reader) + { + LinkToPage.Deserialize(reader, nameof(LinkToPage)); + var page = LinkToPage; + + if ((reader.DeserializeFrom == SerializeTo.Report || page.IsInherit) && !string.IsNullOrEmpty(LinkToPage.PageName) + && !string.IsNullOrEmpty(page.ReportPath) && File.Exists(page.ReportPath)) + LoadExternalPage(page, reader.Report, reader.ReadStr("Name")); + + base.Deserialize(reader); + } + + private void LoadExternalPage(PageLink page, Report parent, string pageName) + { + try + { + using (Report report = new Report()) + { + report.Load(page.ReportPath); + page.IsInherit = report.IsAncestor; + foreach (PageBase item in report.Pages) + { + if (item is ReportPage && item.Name == page.PageName) + { + AssignAll(item, true, true); + SetAncestor(false); + LinkToPage = page; + if (!LinkToPage.SaveNames) + { + Alias = Name; + Name = pageName; + Parent = parent; + SetReport(parent); + foreach (Base obj in AllObjects) + { + obj.Alias = obj.Name; + obj.SetReport(parent); + obj.CreateUniqueName(); + } + } + break; + } + else + { + Clear(); + } + } + } + } + catch + { + Clear(); + } } /// @@ -1271,6 +1510,7 @@ public ReportPage() unlimitedHeight = false; printOnRollPaper = false; unlimitedWidth = false; + pageLink = new PageLink(this); unlimitedHeightValue = MAX_PAPER_SIZE_MM * Units.Millimeters; unlimitedWidthValue = MAX_PAPER_SIZE_MM * Units.Millimeters; } diff --git a/FastReport.Base/Table/TableCell.Async.cs b/FastReport.Base/Table/TableCell.Async.cs new file mode 100644 index 00000000..fe1245a3 --- /dev/null +++ b/FastReport.Base/Table/TableCell.Async.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Table +{ + public partial class TableCell + { + + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion + } +} diff --git a/FastReport.Base/Table/TableCell.cs b/FastReport.Base/Table/TableCell.cs index 16593610..a744ad91 100644 --- a/FastReport.Base/Table/TableCell.cs +++ b/FastReport.Base/Table/TableCell.cs @@ -392,6 +392,11 @@ public override void RestoreState() public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (Table != null && Table.IsInsideSpan(this)) Text = ""; diff --git a/FastReport.Base/Table/TableObject.Async.cs b/FastReport.Base/Table/TableObject.Async.cs new file mode 100644 index 00000000..3fdd923a --- /dev/null +++ b/FastReport.Base/Table/TableObject.Async.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Table +{ + public partial class TableObject + { + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + + GetDataShared(); + } + + #endregion + } +} diff --git a/FastReport.Base/Table/TableObject.cs b/FastReport.Base/Table/TableObject.cs index 2fd922a8..9c6e9c55 100644 --- a/FastReport.Base/Table/TableObject.cs +++ b/FastReport.Base/Table/TableObject.cs @@ -349,6 +349,11 @@ public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (!IsManualBuild) { for (int y = 0; y < Rows.Count; y++) diff --git a/FastReport.Base/TextObject.Async.cs b/FastReport.Base/TextObject.Async.cs new file mode 100644 index 00000000..3a0d6a05 --- /dev/null +++ b/FastReport.Base/TextObject.Async.cs @@ -0,0 +1,21 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport +{ + public partial class TextObject + { + + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + + GetDataShared(); + } + + #endregion + } +} \ No newline at end of file diff --git a/FastReport.Base/TextObject.cs b/FastReport.Base/TextObject.cs index d1a243d4..47dde96e 100644 --- a/FastReport.Base/TextObject.cs +++ b/FastReport.Base/TextObject.cs @@ -1624,6 +1624,11 @@ public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { // process expressions if (AllowExpressions) { diff --git a/FastReport.Base/Utils/FRReader.cs b/FastReport.Base/Utils/FRReader.cs index cf2b4162..3c355ab8 100644 --- a/FastReport.Base/Utils/FRReader.cs +++ b/FastReport.Base/Utils/FRReader.cs @@ -258,7 +258,7 @@ public IFRSerializable Read() curRoot = curItem; GetProps(); - if (report != null && report.IsAncestor) + if (report != null && (report.IsAncestor || report.HasPageLinks)) result = report.FindObject(ReadStr("Name")); if (result == null && curItem.Name != "inherited") { diff --git a/FastReport.Base/Utils/FRWriter.cs b/FastReport.Base/Utils/FRWriter.cs index 211b39e4..33c774c8 100644 --- a/FastReport.Base/Utils/FRWriter.cs +++ b/FastReport.Base/Utils/FRWriter.cs @@ -74,6 +74,7 @@ public class FRWriter : IDisposable //private StringBuilder FText; private object diffObject; private bool saveChildren; + private bool saveExternalPages; private bool writeHeader; private BlobStore blobStore; private SerializeTo serializeTo; @@ -124,6 +125,15 @@ public bool SaveChildren set { saveChildren = value; } } + /// + /// Gets or sets a value that determines whether is necessary to serialize external pages. + /// + public bool SaveExternalPages + { + get { return saveExternalPages; } + set { saveExternalPages = value; } + } + /// /// Gets or sets a value that determines whether is necessary to add xml header. /// diff --git a/FastReport.Base/Utils/FontManager.Gdi.cs b/FastReport.Base/Utils/FontManager.Gdi.cs index 6e7ce128..5be5eb31 100644 --- a/FastReport.Base/Utils/FontManager.Gdi.cs +++ b/FastReport.Base/Utils/FontManager.Gdi.cs @@ -2,6 +2,7 @@ #if !SKIA && !FRCORE && (!MONO || WPF) using System; using System.Diagnostics; +using System.Drawing; using System.Drawing.Text; using System.IO; @@ -19,8 +20,15 @@ public static bool AddFont(string filename) bool success = false; if (File.Exists(filename)) { - PrivateFontCollection.AddFontFile(filename); - success = true; + + bool isInstalled = CheckFontIsInstalled(filename); + + if (!isInstalled) + { + PrivateFontCollection.AddFontFile(filename); + + success = true; + } } else { @@ -29,6 +37,28 @@ public static bool AddFont(string filename) return success; } + /// + /// Checks whether the font from the specified file is installed in the system. + /// + /// The path to the font file. + /// Returns true if the font is installed on the system, otherwise false. + public static bool CheckFontIsInstalled(string filename) + { + PrivateFontCollection tempFontCollection = new PrivateFontCollection(); + tempFontCollection.AddFontFile(filename); + string fontName = tempFontCollection.Families[0].Name; + + + InstalledFontCollection installedFonts = new InstalledFontCollection(); + FontFamily[] fontFamilies = installedFonts.Families; + + // Checking if a font named FontName is installed in the system + // Array.Exists checks if an element in the array exists that satisfies the condition + bool isInstalled = Array.Exists(fontFamilies, family => family.Name.Equals(fontName, StringComparison.OrdinalIgnoreCase)); + + return isInstalled; + } + /// /// Adds a font contained in system memory to this collection. /// diff --git a/FastReport.Base/Utils/HtmlTextRenderer.cs b/FastReport.Base/Utils/HtmlTextRenderer.cs index 185d3373..849d90d5 100644 --- a/FastReport.Base/Utils/HtmlTextRenderer.cs +++ b/FastReport.Base/Utils/HtmlTextRenderer.cs @@ -576,6 +576,36 @@ private void CssStyle(StyleDescriptor style, Dictionary dict) if (dict == null) return; string tStr; + + // If "font-style" contains "italic" or "oblique", apply the Italic style to the text. + if (dict.TryGetValue("font-style", out tStr)) + { + if (tStr.Contains("italic") || tStr.Contains("oblique")) + style.FontStyle |= FontStyle.Italic; + } + + // If "font-weight" contains "bold", apply the Bold style to the text. + if (dict.TryGetValue("font-weight", out tStr)) + { + if (tStr.Contains("bold")) + style.FontStyle |= FontStyle.Bold; + } + + // If "text-decoration" contains both "underline" and "line-through", apply both styles to the text. + // Otherwise, check and apply each style individually. + if (dict.TryGetValue("text-decoration", out tStr)) + { + if (tStr.Contains("underline") && tStr.Contains("line-through")) + style.FontStyle |= FontStyle.Underline | FontStyle.Strikeout; + else + { + if (tStr.Contains("underline")) + style.FontStyle |= FontStyle.Underline; + if (tStr.Contains("line-through")) + style.FontStyle |= FontStyle.Strikeout; + } + } + if (dict.TryGetValue("font-size", out tStr)) { if (EndsWith(tStr, "px")) @@ -3066,6 +3096,26 @@ public void ToHtml(FastString sb, bool close) if (color.A > 0) sb.Append(String.Format(CultureInfo, "color:rgba({0},{1},{2},{3});", color.R, color.G, color.B, ((float)color.A) / 255f)); if (font != null) { sb.Append("font-family:"); sb.Append(font.Name); sb.Append(";"); } if (fontsize > 0) { sb.Append("font-size:"); sb.Append(fontsize.ToString(CultureInfo)); sb.Append("pt;"); } + + //if ((fontStyle & FontStyle.Italic) == FontStyle.Italic) { sb.Append("font-style:italic;"); } + //if ((fontStyle & FontStyle.Bold) == FontStyle.Bold) { sb.Append("font-weight:bold;"); } + + //bool underline = (fontStyle & FontStyle.Underline) == FontStyle.Underline; + //bool strikeout = (fontStyle & FontStyle.Strikeout) == FontStyle.Strikeout; + + //if (underline && strikeout) + //{ + // sb.Append("text-decoration:underline line-through;"); + //} + //else if (underline) + //{ + // sb.Append("text-decoration: underline;"); + //} + //else if (strikeout) + //{ + // sb.Append("text-decoration:line-through;"); + //} + sb.Append("\">"); } } diff --git a/FastReport.Base/Utils/ImageHelper.Async.cs b/FastReport.Base/Utils/ImageHelper.Async.cs new file mode 100644 index 00000000..68ff9868 --- /dev/null +++ b/FastReport.Base/Utils/ImageHelper.Async.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +#if NETCOREAPP +using System.Net.Http; +#endif +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport.Utils +{ + public static partial class ImageHelper + { + internal static async Task LoadAsync(string fileName, CancellationToken cancellationToken) + { + if (!String.IsNullOrEmpty(fileName)) +#if NETCOREAPP + return await File.ReadAllBytesAsync(fileName, cancellationToken); +#else + return File.ReadAllBytes(fileName); +#endif + return null; + } + + internal static async Task LoadURLAsync(Uri url, CancellationToken cancellationToken) + { +#if NETCOREAPP + using (var httpClient = new HttpClient()) + { + return await httpClient.GetByteArrayAsync(url, cancellationToken); + } +#else + ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); + using (var web = new WebClient()) + { + return await web.DownloadDataTaskAsync(url); + } +#endif + } + } +} diff --git a/FastReport.Base/Utils/ImageHelper.cs b/FastReport.Base/Utils/ImageHelper.cs index 701f62ad..5555cd20 100644 --- a/FastReport.Base/Utils/ImageHelper.cs +++ b/FastReport.Base/Utils/ImageHelper.cs @@ -46,7 +46,7 @@ public interface IImageHelperLoader /// Internal calss for image processing /// [EditorBrowsable(EditorBrowsableState.Never)] - public static class ImageHelper + public static partial class ImageHelper { private readonly static object _customLoadersLocker = new object(); private readonly static List _customLoaders = new List(); diff --git a/FastReport.Base/Utils/TextRenderer.cs b/FastReport.Base/Utils/TextRenderer.cs index 130b3ebf..133a23e4 100644 --- a/FastReport.Base/Utils/TextRenderer.cs +++ b/FastReport.Base/Utils/TextRenderer.cs @@ -2110,7 +2110,7 @@ public class InlineImageCache : IDisposable private bool serialized; - private object locker; + private readonly object locker; #endregion Private Fields diff --git a/FastReport.Base/ZipCodeObject.Async.cs b/FastReport.Base/ZipCodeObject.Async.cs new file mode 100644 index 00000000..e8523950 --- /dev/null +++ b/FastReport.Base/ZipCodeObject.Async.cs @@ -0,0 +1,20 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace FastReport +{ + public partial class ZipCodeObject + { + + #region Report Engine + + /// + public override async Task GetDataAsync(CancellationToken cancellationToken) + { + await base.GetDataAsync(cancellationToken); + GetDataShared(); + } + + #endregion + } +} diff --git a/FastReport.Base/ZipCodeObject.cs b/FastReport.Base/ZipCodeObject.cs index 0b327e42..653ec056 100644 --- a/FastReport.Base/ZipCodeObject.cs +++ b/FastReport.Base/ZipCodeObject.cs @@ -338,6 +338,11 @@ public override string[] GetExpressions() public override void GetData() { base.GetData(); + GetDataShared(); + } + + private void GetDataShared() + { if (!String.IsNullOrEmpty(DataColumn)) { object value = Report.GetColumnValue(DataColumn); diff --git a/FastReport.Compat/Directory.Build.props b/FastReport.Compat/Directory.Build.props index cd21a44c..f2978f3c 100644 --- a/FastReport.Compat/Directory.Build.props +++ b/FastReport.Compat/Directory.Build.props @@ -13,7 +13,8 @@ GIT true Common compatible types for FastReport .Net, Core and Mono - frlogo192.png + frlogo192.png + $(PackageIconFileName) true ..\..\FastReport.OpenSource.snk @@ -33,7 +34,7 @@ - + True false diff --git a/FastReport.Core.Web/Application/Cache/IWebReportCache.cs b/FastReport.Core.Web/Application/Cache/IWebReportCache.cs index a73a44c8..d8ce4a50 100644 --- a/FastReport.Core.Web/Application/Cache/IWebReportCache.cs +++ b/FastReport.Core.Web/Application/Cache/IWebReportCache.cs @@ -2,8 +2,10 @@ namespace FastReport.Web.Cache { - - internal interface IWebReportCache : IDisposable + /// + /// Represents the cache where all webReports will be stored + /// + public interface IWebReportCache : IDisposable { void Add(WebReport webReport); diff --git a/FastReport.Core.Web/Application/DesignerSettings.cs b/FastReport.Core.Web/Application/DesignerSettings.cs index 59986591..4ebbdfda 100644 --- a/FastReport.Core.Web/Application/DesignerSettings.cs +++ b/FastReport.Core.Web/Application/DesignerSettings.cs @@ -19,7 +19,7 @@ public class DesignerSettings public bool ScriptCode { get; set; } = false; /// - /// Gets or sets the text of configuration of Online Designer + /// Gets or sets the json of configuration of Online Designer /// public string Config { get; set; } = ""; diff --git a/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs b/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs index 9e30a594..52bb93bf 100644 --- a/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs +++ b/FastReport.Core.Web/Application/Infrastructure/ControllerBuilder.cs @@ -255,7 +255,7 @@ public static Task HandleResult(HttpContext httpContext, object result) { Debug.Assert(result.GetType() != typeof(IActionResult)); - string content = System.Text.Json.JsonSerializer.Serialize(result); + string content = JsonSerializer.Serialize(result); var contentResult = Results.Content(content, "text/json"); return contentResult.ExecuteAsync(httpContext); } diff --git a/FastReport.Core.Web/Application/LinkerFlags.cs b/FastReport.Core.Web/Application/LinkerFlags.cs index aaaa0583..03b9634f 100644 --- a/FastReport.Core.Web/Application/LinkerFlags.cs +++ b/FastReport.Core.Web/Application/LinkerFlags.cs @@ -11,97 +11,4 @@ internal static class LinkerFlags internal const DynamicallyAccessedMemberTypes All = DynamicallyAccessedMemberTypes.All; } - -#if !NET5_0_OR_GREATER - [AttributeUsage( - AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | - AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | - AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, - Inherited = false)] - internal sealed class DynamicallyAccessedMembersAttribute : Attribute - { - /// - /// Initializes a new instance of the class - /// with the specified member types. - /// - /// The types of members dynamically accessed. - public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) - { - } - } - - // - // Summary: - // Specifies the types of members that are dynamically accessed. This enumeration - // has a System.FlagsAttribute attribute that allows a bitwise combination of its - // member values. - [Flags] - internal enum DynamicallyAccessedMemberTypes - { - // - // Summary: - // Specifies all members. - All = -1, - // - // Summary: - // Specifies no members. - None = 0, - // - // Summary: - // Specifies the default, parameterless public constructor. - PublicParameterlessConstructor = 1, - // - // Summary: - // Specifies all public constructors. - PublicConstructors = 3, - // - // Summary: - // Specifies all non-public constructors. - NonPublicConstructors = 4, - // - // Summary: - // Specifies all public methods. - PublicMethods = 8, - // - // Summary: - // Specifies all non-public methods. - NonPublicMethods = 16, - // - // Summary: - // Specifies all public fields. - PublicFields = 32, - // - // Summary: - // Specifies all non-public fields. - NonPublicFields = 64, - // - // Summary: - // Specifies all public nested types. - PublicNestedTypes = 128, - // - // Summary: - // Specifies all non-public nested types. - NonPublicNestedTypes = 256, - // - // Summary: - // Specifies all public properties. - PublicProperties = 512, - // - // Summary: - // Specifies all non-public properties. - NonPublicProperties = 1024, - // - // Summary: - // Specifies all public events. - PublicEvents = 2048, - // - // Summary: - // Specifies all non-public events. - NonPublicEvents = 4096, - // - // Summary: - // Specifies all interfaces implemented by the type. - Interfaces = 8192 - } -#endif } diff --git a/FastReport.Core.Web/Application/WebReport.Backend.cs b/FastReport.Core.Web/Application/WebReport.Backend.cs index 77152d3c..47a6df82 100644 --- a/FastReport.Core.Web/Application/WebReport.Backend.cs +++ b/FastReport.Core.Web/Application/WebReport.Backend.cs @@ -13,6 +13,24 @@ namespace FastReport.Web public partial class WebReport { + private string localizationFile; + + + /// + /// Gets or sets the WebReport's locale + /// + public string LocalizationFile + { + get => localizationFile; + set + { + localizationFile = value; + string path = WebUtils.MapPath(localizationFile); + Res.LoadLocale(path); + } + } + + internal static IResourceLoader ResourceLoader { get; set; } diff --git a/FastReport.Core.Web/Application/WebReport.Tabs.cs b/FastReport.Core.Web/Application/WebReport.Tabs.cs index e17fa5ab..8940263e 100644 --- a/FastReport.Core.Web/Application/WebReport.Tabs.cs +++ b/FastReport.Core.Web/Application/WebReport.Tabs.cs @@ -48,6 +48,12 @@ public int CurrentTabIndex /// public bool SplitReportPagesInTabs { get; set; } = false; + /// + /// Gets or sets a value indicating whether the report name is displayed in the tab. + /// If set to true, the tab will display the report name. If false, it will display report parameters. + /// Default value: false. + /// + public bool ShowReportNameInTab { get; set; } = false; /// /// List of report tabs @@ -122,19 +128,17 @@ internal string GetCurrentTabName() internal string GetTabName(int i) { - - if (String.IsNullOrEmpty(Tabs[i].Name)) + if (string.IsNullOrEmpty(Tabs[i].Name) || ShowReportNameInTab) { - string s = Tabs[i].Report.ReportInfo.Name; - if (String.IsNullOrEmpty(s)) + var s = Tabs[i].Report.ReportInfo.Name; + if (string.IsNullOrEmpty(s)) s = Path.GetFileNameWithoutExtension(Tabs[i].Report.FileName); - if (String.IsNullOrEmpty(s)) + if (string.IsNullOrEmpty(s)) s = (i + 1).ToString(); return s; - } - else - return Tabs[i].Name; + + return Tabs[i].Name; } diff --git a/FastReport.Core.Web/Application/WebReport.cs b/FastReport.Core.Web/Application/WebReport.cs index 0628cef2..9f302cc7 100644 --- a/FastReport.Core.Web/Application/WebReport.cs +++ b/FastReport.Core.Web/Application/WebReport.cs @@ -26,8 +26,6 @@ public enum WebReportMode public partial class WebReport { - private string localizationFile; - #if DIALOGS internal Dialog Dialog { get; } #endif @@ -55,19 +53,12 @@ public Report Report set => Tabs[CurrentTabIndex].Report = value; } - /// - /// Gets or sets the WebReport's locale - /// - public string LocalizationFile - { - get => localizationFile; - set - { - localizationFile = value; - string path = WebUtils.MapPath(localizationFile); - Res.LoadLocale(path); - } - } +#if WASM + [Obsolete("Doesn't support in Wasm. Please, use SetLocalization(Stream) instead", true)] + [EditorBrowsable(EditorBrowsableState.Never)] + public string LocalizationFile { get; set; } +#endif + internal IWebRes Res { get; } = new WebRes(); @@ -222,9 +213,9 @@ internal string InlineStyle public WebReport() { +#if !WASM string path = WebUtils.MapPath(LocalizationFile); Res.LoadLocale(path); -#if !WASM WebReportCache.Instance?.Add(this); #endif #if DIALOGS @@ -237,6 +228,14 @@ static WebReport() ScriptSecurity = new ScriptSecurity(new ScriptChecker()); } + /// + /// Sets WebReport localization using + /// + /// Stream with localization in `*.frl` format + public void SetLocalization(Stream stream) + { + Res.LoadLocale(stream); + } public void LoadPrepared(string filename) { diff --git a/FastReport.Core.Web/Application/WebUtils.cs b/FastReport.Core.Web/Application/WebUtils.cs index 1ac63130..3d9626bf 100644 --- a/FastReport.Core.Web/Application/WebUtils.cs +++ b/FastReport.Core.Web/Application/WebUtils.cs @@ -34,6 +34,7 @@ internal static bool ShouldExportUseZipFormat(IEnumerable> exportParams, Exports export) => ShouldUseZipFormat(exportParams, export); +#if !WASM internal static string MapPath(string path) { if (path.IsNullOrWhiteSpace()) @@ -41,11 +42,9 @@ internal static string MapPath(string path) if (Path.IsPathRooted(path)) return path; -#if !WASM return Path.Combine(FastReportGlobal.HostingEnvironment.ContentRootPath, path); -#endif - return string.Empty; } +#endif internal static string ToUrl(params string[] segments) { @@ -85,7 +84,7 @@ internal static bool IsPng(byte[] image) { byte[] pngHeader = new byte[] { 137, 80, 78, 71, 13, 10, 26, 10 }; bool isPng = true; - for (int i = 0; i < 8; i++) + for (int i = 0; i < 8 && image.Length > 7; i++) if (image[i] != pngHeader[i]) { isPng = false; diff --git a/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs b/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs index 738bf244..ca9fa61c 100644 --- a/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs +++ b/FastReport.Core.Web/Controllers/Designer/ConnectionsController.cs @@ -24,8 +24,8 @@ public sealed class ConnectionsParams public string ConnectionString { get; set; } } - public sealed class ConnectionTablesRequestModel - { + public sealed class ConnectionTablesRequestModel + { public ConnectionsParams ConnectionsParams { get; set; } public List CustomViews { get; set; } @@ -37,9 +37,8 @@ public static IResult GetConnectionTypes([FromQuery] string needSqlSupportInfo, var isNeedSqlSupport = bool.TryParse(needSqlSupportInfo, out var parsedBool) && parsedBool; var response = connectionsService.GetConnectionTypes(isNeedSqlSupport); - var content = "{" + string.Join(",", response.ToArray()) + "}"; - return Results.Content(content, "application/json"); + return Results.Json(response); } [Obsolete] @@ -84,6 +83,54 @@ public static IResult MakeConnectionString(string connectionType, : Results.Content(response, "application/xml"); } + + [HttpPost("/designer.updateConnectionTable")] + public static IResult UpdateConnectionTable([FromQuery] string reportId, + [FromQuery] string connectionType, + [FromBody] UpdateTableParams parameters, + IReportService reportService, + IConnectionsService connectionsService, + HttpRequest request) + { + if (!IsAuthorized(request)) + return Results.Unauthorized(); + + try + { + string response; + + if (parameters.ConnectionString.IsNullOrWhiteSpace()) + { + if (!reportService.TryFindWebReport(reportId, out var webReport)) + return Results.NotFound(); + + response = connectionsService.GetUpdatedTableByReportId(webReport, parameters); + } + else + { + response = connectionsService.GetUpdatedTableByConnectionString(parameters.ConnectionString, + connectionType, parameters); + } + + return Results.Content(response, "application/xml"); + } + catch (Exception ex) + { + return Results.BadRequest(ex.Message); + } + } + + [HttpGet("/designer.getParameterTypes")] + public static IResult GetParameterTypes([FromQuery] string connectionType, + IConnectionsService connectionsService) + { + var response = connectionsService.GetParameterTypes(connectionType, out string error); + + return string.IsNullOrEmpty(error) ? + Results.Json(response) + : Results.BadRequest(error); + } + [HttpGet("/designer.getConnectionStringProperties")] public static IResult GetConnectionStringProperties([FromQuery] ConnectionsParams query, IConnectionsService connectionsService) diff --git a/FastReport.Core.Web/Controllers/Preview/GetReportController.cs b/FastReport.Core.Web/Controllers/Preview/GetReportController.cs index 800fd0b4..f796138b 100644 --- a/FastReport.Core.Web/Controllers/Preview/GetReportController.cs +++ b/FastReport.Core.Web/Controllers/Preview/GetReportController.cs @@ -6,6 +6,7 @@ using System.Net.Mime; using System.Threading.Tasks; using FastReport.Utils; +using System.Threading; namespace FastReport.Web.Controllers { @@ -14,9 +15,10 @@ static partial class Controllers private const string INVALID_REPORT_MESSAGE = "Error loading report: The report structure is invalid."; [HttpPost("/preview.getReport")] - public static IResult GetReport([FromQuery] string reportId, + public static async Task GetReport([FromQuery] string reportId, IReportService reportService, - HttpRequest request) + HttpRequest request, + CancellationToken cancellationToken) { if (!IsAuthorized(request)) return Results.Unauthorized(); @@ -28,7 +30,7 @@ public static IResult GetReport([FromQuery] string reportId, try { - string render = reportService.GetReport(webReport, query); + string render = await reportService.GetReportAsync(webReport, query, cancellationToken); if (render.IsNullOrEmpty()) return Results.Ok(); diff --git a/FastReport.Core.Web/Directory.Build.targets b/FastReport.Core.Web/Directory.Build.targets index 18cedec9..bd0a6a03 100644 --- a/FastReport.Core.Web/Directory.Build.targets +++ b/FastReport.Core.Web/Directory.Build.targets @@ -3,9 +3,9 @@ - + - + \ No newline at end of file diff --git a/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs b/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs index bcefe128..ec5b99e3 100644 --- a/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs +++ b/FastReport.Core.Web/Services/Abstract/IConnectionsService.cs @@ -34,9 +34,34 @@ public interface IConnectionsService /// Returns JSON with connected tables string GetConnectionTables(string connectionType, string connectionString, List customConnections); + /// + /// Updates a table within a web report based on the provided parameters. + /// + /// The WebReport object containing the table to be updated. + /// The parameters specifying the table name, SQL query, and update parameters. + /// A string representation of the updated table. + string GetUpdatedTableByReportId(WebReport webReport, UpdateTableParams parameters); + + /// + /// Updates a table within a database using the provided connection string and parameters. + /// + /// The connection string to the database. + /// The type of connection. + /// The parameters specifying the table name, SQL query, and update parameters. + /// A string representation of the updated table. + string GetUpdatedTableByConnectionString(string connectionString, string connectionType, UpdateTableParams parameters); + /// /// Returns the list of connection types /// - List GetConnectionTypes(bool needSqlSupportInfo = false); + Dictionary GetConnectionTypes(bool needSqlSupportInfo = false); + + /// + /// Returns the list of parameter types by connection type. + /// + /// The type of connection. + /// Error message. + /// + Dictionary GetParameterTypes(string connectionType, out string errorMsg); } } diff --git a/FastReport.Core.Web/Services/Abstract/IReportService.cs b/FastReport.Core.Web/Services/Abstract/IReportService.cs index 4dc45d44..df6ce2ce 100644 --- a/FastReport.Core.Web/Services/Abstract/IReportService.cs +++ b/FastReport.Core.Web/Services/Abstract/IReportService.cs @@ -15,14 +15,6 @@ namespace FastReport.Web.Services /// public interface IReportService { - /// - /// Returns a report for Preview on the Web - /// - /// Report a preview of which you want to create - /// Report preview creation options - /// Returns the HTML string of the report preview - string GetReport(WebReport webReport, GetReportServiceParams @params); - /// /// Asynchronously returns a report for Preview on the Web /// diff --git a/FastReport.Core.Web/Services/Implementation/ConnectionService.cs b/FastReport.Core.Web/Services/Implementation/ConnectionService.cs index 56277ef6..7b7f72cf 100644 --- a/FastReport.Core.Web/Services/Implementation/ConnectionService.cs +++ b/FastReport.Core.Web/Services/Implementation/ConnectionService.cs @@ -8,7 +8,6 @@ using System.Data; using System.IO; using System.Linq; -using System.Net; using System.Text; using System.Text.Encodings.Web; @@ -16,20 +15,22 @@ namespace FastReport.Web.Services { internal sealed class ConnectionService : IConnectionsService { - - public string GetConnectionStringPropertiesJSON(string connectionType, string connectionString, out bool isError) + private static Type GetConnectionType(string connectionType) { var objects = new List(); RegisteredObjects.DataConnections.EnumItems(objects); - Type connType = null; foreach (var info in objects) - if (info.Object != null && - info.Object.FullName == connectionType) + if (info.Object != null && info.Object.FullName == connectionType) { - connType = info.Object; - break; + return info.Object; } + return null; + } + + public string GetConnectionStringPropertiesJSON(string connectionType, string connectionString, out bool isError) + { + Type connType = GetConnectionType(connectionType); if (connType == null) { @@ -67,8 +68,8 @@ public string GetConnectionStringPropertiesJSON(string connectionType, string co try { object owner = conn; - if (conn is ICustomTypeDescriptor) - owner = ((ICustomTypeDescriptor)conn).GetPropertyOwner(pd); + if (conn is ICustomTypeDescriptor customTypeDescriptor) + owner = customTypeDescriptor.GetPropertyOwner(pd); value = pd.GetValue(owner); } catch { } @@ -95,17 +96,7 @@ public string GetConnectionStringPropertiesJSON(string connectionType, string co public string CreateConnectionStringJSON(string connectionType, IFormCollection form, out bool isError) { - var objects = new List(); - RegisteredObjects.DataConnections.EnumItems(objects); - Type connType = null; - - foreach (var info in objects) - if (info.Object != null && - info.Object.FullName == connectionType) - { - connType = info.Object; - break; - } + Type connType = GetConnectionType(connectionType); if (connType == null) { @@ -141,8 +132,8 @@ public string CreateConnectionStringJSON(string connectionType, IFormCollection object value = typeConverter.ConvertFromString(propertyValue); object owner = conn; - if (conn is ICustomTypeDescriptor) - owner = ((ICustomTypeDescriptor)conn).GetPropertyOwner(pd); + if (conn is ICustomTypeDescriptor customTypeDescriptor) + owner = customTypeDescriptor.GetPropertyOwner(pd); pd.SetValue(owner, value); } catch (Exception ex) @@ -166,73 +157,138 @@ public string CreateConnectionStringJSON(string connectionType, IFormCollection public string GetConnectionTables(string connectionType, string connectionString, List customViews) { if (!IsConnectionStringValid(connectionString, out var errorMsg)) - { throw new Exception(errorMsg); - } - var objects = new List(); - RegisteredObjects.DataConnections.EnumItems(objects); - Type connType = null; + try + { + using var conn = CreateConnection(connectionType); - foreach (var info in objects) - if (info.Object != null && - info.Object.FullName == connectionType) + conn.ConnectionString = connectionString; + + if (FastReportGlobal.AllowCustomSqlQueries) { - connType = info.Object; - break; + foreach (var view in customViews) + { + var source = new TableDataSource + { + Table = new DataTable(), + TableName = view.TableName, + Name = view.TableName, + SelectCommand = view.SqlQuery + }; + + conn.Tables.Add(source); + conn.DataSet.Tables.Add(source.Table); + } } - if (connType != null) + conn.CreateAllTables(true); + + foreach (TableDataSource c in conn.Tables) + c.Enabled = true; + + return SerializeToString(conn); + } + catch + { + throw new Exception("Error in creating tables. Please verify your connection string."); + } + } + + public string GetUpdatedTableByReportId(WebReport webReport, UpdateTableParams parameters) + { + var dataSource = webReport.Report.GetDataSource(parameters.TableName) as TableDataSource + ?? throw new Exception("Table not found"); + + try { - try + foreach (var parameter in parameters.Parameters) { - using (DataConnectionBase conn = (DataConnectionBase)Activator.CreateInstance(connType)) - using (var writer = new FRWriter()) - { - conn.ConnectionString = connectionString; + ApplyParameterToDataSource(dataSource, parameter); + } - if (FastReportGlobal.AllowCustomSqlQueries) - { - foreach (var view in customViews) - { - var source = new TableDataSource - { - Table = new DataTable(), - TableName = view.TableName, - Name = view.TableName, - SelectCommand = view.SqlQuery - }; - - conn.Tables.Add(source); - conn.DataSet.Tables.Add(source.Table); - } - } + dataSource.SelectCommand = parameters.SqlQuery; + dataSource.RefreshTable(); - conn.CreateAllTables(true); - foreach (TableDataSource c in conn.Tables) - c.Enabled = true; + return SerializeToString(dataSource); + } + catch (Exception ex) + { + throw new Exception("Error in creating tables. Please verify your parameters.", ex); + } + } - writer.SaveChildren = true; - writer.WriteHeader = false; - writer.Write(conn); + public string GetUpdatedTableByConnectionString(string connectionString, string connectionType, + UpdateTableParams parameters) + { + if (!IsConnectionStringValid(connectionString, out var errorMsg)) + throw new ArgumentException(errorMsg); - using (var ms = new MemoryStream()) - { - writer.Save(ms); - ms.Position = 0; + try + { + using var conn = CreateConnection(connectionType); + conn.ConnectionString = connectionString; + conn.CreateAllTables(true); - return Encoding.UTF8.GetString(ms.ToArray()); - } - } - } - catch + var dataSource = conn.Tables.Cast() + .FirstOrDefault(table => string.Equals(table.TableName, parameters.TableName)) + ?? throw new ArgumentException("Table not found"); + + foreach (var parameter in parameters.Parameters) { - throw new Exception("Error in creating tables. Please verify your connection string."); + ApplyParameterToDataSource(dataSource, parameter); } + + dataSource.SelectCommand = parameters.SqlQuery; + dataSource.RefreshTable(); + + return SerializeToString(dataSource); } + catch (Exception ex) + { + throw new Exception("Error updating table in the database.", ex); + } + } - throw new Exception("Connection type not found"); + private static CommandParameter CreateParameter(ParameterModel parameterParams) + { + return new CommandParameter + { + Name = parameterParams.Name ?? string.Empty, + Value = parameterParams.Value ?? string.Empty, + DataType = parameterParams.DataType, + Expression = parameterParams.Expression ?? string.Empty + }; + } + + private static DataConnectionBase CreateConnection(string connectionType) + { + var connType = GetConnectionType(connectionType); + return connType == null + ? throw new ArgumentException("Connection type not found") + : (DataConnectionBase)Activator.CreateInstance(connType); + } + + private static string SerializeToString(IFRSerializable serializable) + { + using var writer = new FRWriter(); + writer.SaveChildren = true; + writer.WriteHeader = false; + writer.Write(serializable); + + using var ms = new MemoryStream(); + writer.Save(ms); + ms.Position = 0; + + using var reader = new StreamReader(ms, Encoding.UTF8); + return reader.ReadToEnd(); + } + + private static void ApplyParameterToDataSource(TableDataSource dataSource, ParameterModel parameterModel) + { + var parameter = CreateParameter(parameterModel); + dataSource.Parameters.Add(parameter); } private static bool IsConnectionStringValid(string connectionString, out string errorMsg) @@ -247,26 +303,62 @@ private static bool IsConnectionStringValid(string connectionString, out string return true; } - public List GetConnectionTypes(bool needSqlSupportInfo = false) + public Dictionary GetConnectionTypes(bool needSqlSupportInfo = false) { - var result = new List(); + var result = new Dictionary(); var objects = new List(); RegisteredObjects.DataConnections.EnumItems(objects); foreach (var info in objects.Where(info => info.Object != null)) { - string connection; - if (needSqlSupportInfo) { using var conn = (DataConnectionBase)Activator.CreateInstance(info.Object); - connection = $"\"{info.Object.FullName}\": {GetConnectionJson(info.Text, conn.IsSqlBased)}"; + result.Add(info.Object.FullName, GetConnectionJson(info.Text, conn.IsSqlBased)); } - else connection = $"\"{info.Object.FullName}\": \"{Res.TryGetBuiltin(info.Text)}\""; + else + result.Add(info.Object.FullName, Res.TryGetBuiltin(info.Text)); + } + + return result; + } + + public Dictionary GetParameterTypes(string connectionType, out string errorMsg) + { + var result = new Dictionary(); + Type connType = GetConnectionType(connectionType); + + if (connType == null) + { + errorMsg = "Connection type not found"; + return result; + } - result.Add(connection); + try + { + using (var conn = (DataConnectionBase)Activator.CreateInstance(connType)) + { + Array values; + + var paramType = conn.GetParameterType(); + if (paramType != null) + values = Enum.GetValues(paramType); + else + values = Enum.GetValues(); + + foreach (var par in values) + { + result.Add(par.ToString(), (int)par); + } + } + } + catch (Exception ex) + { + errorMsg = ex.ToString(); + return result; } + errorMsg = ""; return result; } diff --git a/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs b/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs index cba49fef..7b4beaf7 100644 --- a/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs +++ b/FastReport.Core.Web/Services/Implementation/InternalResourceLoader.cs @@ -78,6 +78,7 @@ public byte[] GetBytes(string name) var buffer = new byte[resourceStream.Length]; resourceStream.Read(buffer, 0, buffer.Length); + cache2[name] = buffer; return buffer; } @@ -93,7 +94,7 @@ public async ValueTask GetBytesAsync(string name, CancellationToken canc return null; var buffer = new byte[resourceStream.Length]; - await resourceStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); + await resourceStream.ReadAsync(buffer, cancellationToken); cache2[name] = buffer; return buffer; } diff --git a/FastReport.Core.Web/Services/Implementation/ReportService.cs b/FastReport.Core.Web/Services/Implementation/ReportService.cs index d4c3c199..7399d9e9 100644 --- a/FastReport.Core.Web/Services/Implementation/ReportService.cs +++ b/FastReport.Core.Web/Services/Implementation/ReportService.cs @@ -21,7 +21,8 @@ public ReportService(IWebReportCache webReportCache) _cache = webReportCache; } - public string GetReport(WebReport webReport, GetReportServiceParams @params) + + public async Task GetReportAsync(WebReport webReport, GetReportServiceParams @params, CancellationToken cancellationToken = default) { #if DIALOGS webReport.Dialogs(@params.DialogParams); @@ -31,13 +32,16 @@ public string GetReport(WebReport webReport, GetReportServiceParams @params) if (webReport.Mode == WebReportMode.Dialog) { - webReport.Report.PreparePhase1(); + await webReport.Report.PreparePhase1Async(cancellationToken); } else #endif if (!webReport.ReportPrepared && @params.SkipPrepare != "yes" || @params.ForceRefresh == "yes") { - webReport.Report.Prepare(); + // don't reset the data state if we run the dialog page or refresh a report. + // This is necessary to keep data filtering settings alive + var resetDataState = @params.ForceRefresh != "yes" && string.IsNullOrEmpty(@params.DialogParams.DialogN); + await webReport.Report.PrepareAsync(false, resetDataState, cancellationToken); webReport.SplitReportPagesByTabs(); } @@ -54,11 +58,6 @@ public string GetReport(WebReport webReport, GetReportServiceParams @params) return webReport.Render(renderBodyBool).ToString(); } - public Task GetReportAsync(WebReport webReport, GetReportServiceParams @params, CancellationToken cancellationToken = default) - { - return Task.FromResult(GetReport(webReport, @params)); - } - public async Task InvokeCustomElementAction(WebReport webReport, string elementId, string inputValue) { var element = webReport.Toolbar.Elements.FirstOrDefault(e => diff --git a/FastReport.Core.Web/Services/ServicesParamsModels.cs b/FastReport.Core.Web/Services/ServicesParamsModels.cs index bcf7e5b7..4e5d2a3d 100644 --- a/FastReport.Core.Web/Services/ServicesParamsModels.cs +++ b/FastReport.Core.Web/Services/ServicesParamsModels.cs @@ -44,6 +44,22 @@ public sealed class CustomViewModel public string SqlQuery { get; set; } } + public class UpdateTableParams + { + public string ConnectionString { get; set; } + public string SqlQuery { get; set; } + public List Parameters { get; set; } + public string TableName { get; set; } + } + + public sealed class ParameterModel + { + public string Name { get; set; } + public int DataType { get; set; } + public string Value { get; set; } + public string Expression { get; set; } + } + #region GetReportServiceParams public class GetReportServiceParams { diff --git a/FastReport.OpenSource/Engine/ReportEngine.Dialogs.OpenSource.cs b/FastReport.OpenSource/Engine/ReportEngine.Dialogs.OpenSource.cs index aa0336e5..cd4019e8 100644 --- a/FastReport.OpenSource/Engine/ReportEngine.Dialogs.OpenSource.cs +++ b/FastReport.OpenSource/Engine/ReportEngine.Dialogs.OpenSource.cs @@ -1,4 +1,5 @@ using FastReport.Dialog; +using System.Threading.Tasks; using System.Windows.Forms; namespace FastReport.Engine @@ -13,6 +14,11 @@ private bool RunDialogs() return true; } + private Task RunDialogsAsync() + { + return Task.FromResult(true); + } + #endregion Private Methods } } diff --git a/FastReport/Resources/en.xml b/FastReport/Resources/en.xml index 013fb170..8996c7df 100644 --- a/FastReport/Resources/en.xml +++ b/FastReport/Resources/en.xml @@ -796,7 +796,8 @@ - + + @@ -1846,6 +1847,8 @@ + + @@ -2862,8 +2865,8 @@ - - + + @@ -2888,19 +2891,21 @@ + + - - + + - - + + diff --git a/Localization/Arabic.frl b/Localization/Arabic.frl index 2cf79cee..abd20da9 100644 --- a/Localization/Arabic.frl +++ b/Localization/Arabic.frl @@ -1960,8 +1960,8 @@ - - + + @@ -1972,11 +1972,11 @@ - - + + - - + + diff --git a/Localization/Armenian.frl b/Localization/Armenian.frl index 7e5b37b8..5c4cabb1 100644 --- a/Localization/Armenian.frl +++ b/Localization/Armenian.frl @@ -1854,14 +1854,14 @@ - - + + - - + + - - + + diff --git a/Localization/Croatian.frl b/Localization/Croatian.frl index 082ca508..bbdf4215 100644 --- a/Localization/Croatian.frl +++ b/Localization/Croatian.frl @@ -1475,11 +1475,11 @@ - - + + - - + + diff --git a/Localization/Czech.frl b/Localization/Czech.frl index 5eab842a..cd6f8ddb 100644 --- a/Localization/Czech.frl +++ b/Localization/Czech.frl @@ -1577,14 +1577,14 @@ - - + + - - + + - - + + diff --git a/Localization/Danish.frl b/Localization/Danish.frl index 7566350b..cd9c769b 100644 --- a/Localization/Danish.frl +++ b/Localization/Danish.frl @@ -1488,11 +1488,11 @@ - - + + - - + + diff --git a/Localization/Dutch.frl b/Localization/Dutch.frl index 7a136853..e0c26839 100644 --- a/Localization/Dutch.frl +++ b/Localization/Dutch.frl @@ -1488,11 +1488,11 @@ - - + + - - + + diff --git a/Localization/French.frl b/Localization/French.frl index a626e369..8c2ff47e 100644 --- a/Localization/French.frl +++ b/Localization/French.frl @@ -1923,17 +1923,17 @@ - - + + - - + + - - + + diff --git a/Localization/German.frl b/Localization/German.frl index 1fe063dc..2b17ddb6 100644 --- a/Localization/German.frl +++ b/Localization/German.frl @@ -2734,8 +2734,8 @@ - - + + @@ -2765,11 +2765,11 @@ - - + + - - + + diff --git a/Localization/Greek.frl b/Localization/Greek.frl index fef35c50..5295f289 100644 --- a/Localization/Greek.frl +++ b/Localization/Greek.frl @@ -2247,8 +2247,8 @@ - - + + @@ -2259,11 +2259,11 @@ - - + + - - + + diff --git a/Localization/Hungarian.frl b/Localization/Hungarian.frl index 81b4506b..f4bc9e3a 100644 --- a/Localization/Hungarian.frl +++ b/Localization/Hungarian.frl @@ -1658,14 +1658,14 @@ - - + + - - + + - - + + diff --git a/Localization/Persian.frl b/Localization/Persian.frl index 010a0860..dec0f271 100644 --- a/Localization/Persian.frl +++ b/Localization/Persian.frl @@ -1476,11 +1476,11 @@ - - + + - - + + diff --git a/Localization/Romanian.frl b/Localization/Romanian.frl index 28e06866..002decd3 100644 --- a/Localization/Romanian.frl +++ b/Localization/Romanian.frl @@ -1498,14 +1498,14 @@ - - + + - - + + - - + + diff --git a/Localization/Russian.frl b/Localization/Russian.frl index 02fd05ac..88d9dec6 100644 --- a/Localization/Russian.frl +++ b/Localization/Russian.frl @@ -1,4 +1,4 @@ - + @@ -197,8 +197,8 @@ - + @@ -794,7 +794,8 @@ - + + @@ -1661,6 +1662,8 @@ + + @@ -1797,7 +1800,7 @@ - + @@ -1812,7 +1815,7 @@ - + @@ -2037,7 +2040,6 @@ - @@ -2189,8 +2191,8 @@ - + @@ -2211,9 +2213,9 @@ - - - + + + @@ -2629,8 +2631,8 @@ - - + + @@ -2655,19 +2657,21 @@ + + - - + + - - + + diff --git a/Localization/Slovak.frl b/Localization/Slovak.frl index 0d315334..68b8c3a9 100644 --- a/Localization/Slovak.frl +++ b/Localization/Slovak.frl @@ -1490,14 +1490,14 @@ - - + + - - + + - - + + diff --git a/Localization/Slovenian.frl b/Localization/Slovenian.frl index 4475d219..2ad7c03d 100644 --- a/Localization/Slovenian.frl +++ b/Localization/Slovenian.frl @@ -1878,14 +1878,14 @@ - - + + - - + + - - + + diff --git a/Localization/Spanish.frl b/Localization/Spanish.frl index 9c0e4da0..bd0d4305 100644 --- a/Localization/Spanish.frl +++ b/Localization/Spanish.frl @@ -1760,14 +1760,14 @@ - - + + - - + + - - + + diff --git a/Localization/Thai.frl b/Localization/Thai.frl index 48678c64..fd4fb550 100644 --- a/Localization/Thai.frl +++ b/Localization/Thai.frl @@ -1361,10 +1361,10 @@ - - - - + + + + diff --git a/Localization/Ukrainian.frl b/Localization/Ukrainian.frl index 0e1ee4a5..fff51a9c 100644 --- a/Localization/Ukrainian.frl +++ b/Localization/Ukrainian.frl @@ -1487,11 +1487,11 @@ - - + + - - + + diff --git a/Pack/BuildScripts/Tasks/BaseTasks.cs b/Pack/BuildScripts/Tasks/BaseTasks.cs index c66c909e..59eb62ad 100644 --- a/Pack/BuildScripts/Tasks/BaseTasks.cs +++ b/Pack/BuildScripts/Tasks/BaseTasks.cs @@ -205,4 +205,7 @@ internal string GetOutdir(string projectOrPath) var additionDir = GetDirNameByProject(project); return Path.Combine(outdir, additionDir); } + + + partial void UseNugetLocalization(NuGetPackSettings settings, ProductType productType); } diff --git a/Pack/BuildScripts/Tasks/LocalizationPackage.cs b/Pack/BuildScripts/Tasks/LocalizationPackage.cs index 6b93f98f..a7e99ca5 100644 --- a/Pack/BuildScripts/Tasks/LocalizationPackage.cs +++ b/Pack/BuildScripts/Tasks/LocalizationPackage.cs @@ -42,19 +42,21 @@ public void FastReportLocalization() var nuGetPackSettings = new NuGetPackSettings { Id = projName, - Authors = new[] { "Fast Reports Inc." }, - Owners = new[] { "Fast Reports Inc." }, - Description = "FastReport.Localization includes localization files for FastReport .NET, FastReport.Core, FastReport.WPF, FastReport.Mono and FastReport.OpenSource", + Authors = ["Fast Reports Inc."], + Owners = ["Fast Reports Inc."], + Description = "FastReport.Localization includes localization files for FastReport .NET, FastReport.Core, FastReport.WPF, FastReport.Avalonia, FastReport.Mono and FastReport.OpenSource", ProjectUrl = new Uri("https://www.fast-report.com/en/product/fast-report-net"), Icon = FRLOGO192PNG, License = new NuSpecLicense { Type = "file", Value = MIT_LICENSE }, - Tags = new[] { "fastreport", "localization" , "frl"}, + Tags = ["fastreport", "localization", "frl"], Version = version, BasePath = tempDir, OutputDirectory = GetOutdir(projName), Files = packFiles, }; + UseNugetLocalization(nuGetPackSettings, ProductType.Localization); + // pack NuGetPack(nuGetPackSettings); } diff --git a/UsedPackages.version b/UsedPackages.version index 10d0c7b4..d34fd136 100644 --- a/UsedPackages.version +++ b/UsedPackages.version @@ -4,14 +4,14 @@ - 2024.2.0 + 2025.1.0 2.88.6 2.88.6 [11.0.6,) - 2024.2.0 + 2025.1.0 [4.0.1,) [4.0.1,) @@ -25,9 +25,9 @@ - 2024.2.0 + 2025.1.0 - 2024.2.0 + 2025.1.0