From 0b23f3bc0d17999f1a23896fc516b9c86d31fbeb Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 9 Aug 2023 18:47:24 +0200 Subject: [PATCH 001/153] Add logic to convert any array to object array. (#731) --- src-console/ConsoleApp_net6.0/Program.cs | 53 ++- .../Parser/ExpressionHelper.cs | 36 +- .../Parser/ExpressionParser.cs | 16 +- .../Parser/IExpressionHelper.cs | 2 + .../Parser/SupportedMethods/MethodFinder.cs | 34 +- .../DynamicExpressionParserTests.cs | 63 +++ .../Parser/ExpressionHelperTests.cs | 435 +++++++++--------- 7 files changed, 411 insertions(+), 228 deletions(-) diff --git a/src-console/ConsoleApp_net6.0/Program.cs b/src-console/ConsoleApp_net6.0/Program.cs index b471cac20..15323cc92 100644 --- a/src-console/ConsoleApp_net6.0/Program.cs +++ b/src-console/ConsoleApp_net6.0/Program.cs @@ -1,6 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using System.Linq.Expressions; namespace ConsoleApp_net6._0 { @@ -19,6 +21,11 @@ class Program { static void Main(string[] args) { + Issue389DoesNotWork(); + return; + Issue389_Works(); + return; + var q = new[] { new X { Key = "x" }, @@ -32,6 +39,50 @@ static void Main(string[] args) Dynamic(); } + private static void Issue389_Works() + { + var strArray = new[] { "1", "2", "3", "4" }; + var x = new List(); + x.Add(Expression.Parameter(strArray.GetType(), "strArray")); + + string query = "string.Join(\",\", strArray)"; + + var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query); + Delegate del = e.Compile(); + var result1 = del.DynamicInvoke(new object?[] { strArray }); + Console.WriteLine(result1); + } + + private static void Issue389WorksWithInts() + { + var intArray = new object[] { 1, 2, 3, 4 }; + var x = new List(); + x.Add(Expression.Parameter(intArray.GetType(), "intArray")); + + string query = "string.Join(\",\", intArray)"; + + var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query); + Delegate del = e.Compile(); + var result = del.DynamicInvoke(new object?[] { intArray }); + + Console.WriteLine(result); + } + + private static void Issue389DoesNotWork() + { + var intArray = new [] { 1, 2, 3, 4 }; + var x = new List(); + x.Add(Expression.Parameter(intArray.GetType(), "intArray")); + + string query = "string.Join(\",\", intArray)"; + + var e = DynamicExpressionParser.ParseLambda(x.ToArray(), null, query); + Delegate del = e.Compile(); + var result = del.DynamicInvoke(new object?[] { intArray }); + + Console.WriteLine(result); + } + private static void Normal() { var e = new int[0].AsQueryable(); diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs index fa6a6b952..b5e722fe8 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionHelper.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq.Dynamic.Core.Validation; @@ -302,11 +303,6 @@ public Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression ex #endif } - private Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) - { - return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); - } - private void WrapConstantExpressions(ref Expression left, ref Expression right) { if (_parsingConfig.UseParameterizedNamesInDynamicQuery) @@ -359,6 +355,17 @@ public Expression GenerateDefaultExpression(Type type) #endif } + public Expression ConvertAnyArrayToObjectArray(Expression arrayExpression) + { + Check.NotNull(arrayExpression); + + return Expression.Call( + null, + typeof(ExpressionHelper).GetMethod(nameof(ConvertIfIEnumerableHasValues), BindingFlags.Static | BindingFlags.NonPublic)!, + arrayExpression + ); + } + private Expression? GetMemberExpression(Expression? expression) { if (ExpressionQualifiesForNullPropagation(expression)) @@ -436,6 +443,11 @@ private List CollectExpressions(bool addSelf, Expression sourceExpre return list; } + private static Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) + { + return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + } + private static MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) { var methodInfo = left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); @@ -463,4 +475,16 @@ private static MethodInfo GetStaticMethod(string methodName, Expression left, Ex { return unaryExpression?.Operand; } + + private static object[] ConvertIfIEnumerableHasValues(IEnumerable? input) + { + // ReSharper disable once PossibleMultipleEnumeration + if (input != null && input.Cast().Any()) + { + // ReSharper disable once PossibleMultipleEnumeration + return input.Cast().ToArray(); + } + + return new object[0]; + } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index dca30c443..3d3768224 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -74,8 +74,8 @@ public ExpressionParser(ParameterExpression[]? parameters, string expression, ob _keywordsHelper = new KeywordsHelper(_parsingConfig); _textParser = new TextParser(_parsingConfig, expression); _numberParser = new NumberParser(parsingConfig); - _methodFinder = new MethodFinder(_parsingConfig); _expressionHelper = new ExpressionHelper(_parsingConfig); + _methodFinder = new MethodFinder(_parsingConfig, _expressionHelper); _typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper); _typeConverterFactory = new TypeConverterFactory(_parsingConfig); @@ -1725,10 +1725,15 @@ private bool TryGenerateConversion(Expression sourceExpression, Type destination private Expression ParseMemberAccess(Type? type, Expression? expression) { + var isStaticAccess = false; if (expression != null) { type = expression.Type; } + else + { + isStaticAccess = true; + } int errorPos = _textParser.CurrentToken.Pos; string id = GetIdentifier(); @@ -1736,18 +1741,18 @@ private Expression ParseMemberAccess(Type? type, Expression? expression) if (_textParser.CurrentToken.Id == TokenId.OpenParen) { - if (expression != null && type != typeof(string)) + if (!isStaticAccess && type != typeof(string)) { var enumerableType = TypeHelper.FindGenericType(typeof(IEnumerable<>), type); if (enumerableType != null) { Type elementType = enumerableType.GetTypeInfo().GetGenericTypeArguments()[0]; - return ParseEnumerable(expression, elementType, id, errorPos, type); + return ParseEnumerable(expression!, elementType, id, errorPos, type); } } Expression[] args = ParseArgumentList(); - switch (_methodFinder.FindMethod(type, id, expression == null, ref expression, ref args, out var methodBase)) + switch (_methodFinder.FindMethod(type, id, isStaticAccess, ref expression, ref args, out var methodBase)) { case 0: throw ParseError(errorPos, Res.NoApplicableMethod, id, TypeHelper.GetTypeName(type)); @@ -1764,9 +1769,10 @@ private Expression ParseMemberAccess(Type? type, Expression? expression) var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter); var typeArguments = genericParameters.Select(a => args[a.Position].Type); var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray()); + return Expression.Call(expression, constructedMethod, args); } - + return Expression.Call(expression, method, args); default: diff --git a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs index c09cf5963..ce4b902e9 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IExpressionHelper.cs @@ -46,4 +46,6 @@ internal interface IExpressionHelper Expression ConvertToExpandoObjectAndCreateDynamicExpression(Expression expression, Type type, string propertyName); Expression GenerateDefaultExpression(Type type); + + Expression ConvertAnyArrayToObjectArray(Expression arrayExpression); } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 40b418487..3a926a2e3 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; using System.Reflection; @@ -7,14 +8,12 @@ namespace System.Linq.Dynamic.Core.Parser.SupportedMethods internal class MethodFinder { private readonly ParsingConfig _parsingConfig; + private readonly IExpressionHelper _expressionHelper; - /// - /// Get an instance - /// - /// - public MethodFinder(ParsingConfig parsingConfig) + public MethodFinder(ParsingConfig parsingConfig, IExpressionHelper expressionHelper) { - _parsingConfig = parsingConfig; + _parsingConfig = Check.NotNull(parsingConfig); + _expressionHelper = Check.NotNull(expressionHelper); } public bool ContainsMethod(Type type, string methodName, bool staticAccess, Expression? instance, ref Expression[] args) @@ -116,7 +115,26 @@ public int FindBestMethodBasedOnArguments(IEnumerable methods, ref E method = methodData.MethodBase; } - args = methodData.Args; + if (args.Length == 0 || args.Length != methodData.Args.Length) + { + args = methodData.Args; + } + else + { + for (var i = 0; i < args.Length; i++) + { + if (args[i].Type != methodData.Args[i].Type && + args[i].Type.IsArray && methodData.Args[i].Type.IsArray && + args[i].Type != typeof(string) && methodData.Args[i].Type == typeof(object[])) + { + args[i] = _expressionHelper.ConvertAnyArrayToObjectArray(args[i]); + } + else + { + args[i] = methodData.Args[i]; + } + } + } } else { @@ -134,7 +152,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method) if (members.Length != 0) { IEnumerable methods = members.OfType(). -#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) +#if !(NETFX_CORE || WINDOWS_APP || UAP10_0 || NETSTANDARD) Select(p => (MethodBase)p.GetGetMethod()). Where(m => m != null); #else diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 73cfedc6b..acf8748e2 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -1696,6 +1696,69 @@ public void DynamicExpressionParser_ParseComparisonOperator_DynamicClass_For_Str isValid.Should().BeTrue(); } + [Fact] + public void DynamicExpressionParser_ParseLambda_HandleStringArray_For_StringJoin() + { + // Arrange + var strArray = new[] { "a", "b", "c" }; + var parameterExpressions = new List + { + Expression.Parameter(strArray.GetType(), "strArray") + }; + + var expression = "string.Join(\",\", strArray)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression); + var @delegate = lambdaExpression.Compile(); + var result = (string)@delegate.DynamicInvoke(new object[] { strArray }); + + // Assert + result.Should().Be("a,b,c"); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_HandleObjectArray_For_StringJoin() + { + // Arrange + var objectArray = new object[] { 1, 2, 3 }; + var parameterExpressions = new List + { + Expression.Parameter(objectArray.GetType(), "objectArray") + }; + + var expression = "string.Join(\",\", objectArray)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression); + var @delegate = lambdaExpression.Compile(); + var result = (string)@delegate.DynamicInvoke(new object[] { objectArray }); + + // Assert + result.Should().Be("1,2,3"); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin() + { + // Arrange + var intArray = new[] { 1, 2, 3 }; + var parameterExpressions = new List + { + Expression.Parameter(intArray.GetType(), "intArray") + }; + + var expression = "string.Join(\",\", intArray)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(parameterExpressions.ToArray(), null, expression); + var @delegate = lambdaExpression.Compile(); + var result = (string)@delegate.DynamicInvoke(intArray); + + // Assert + result.Should().Be("1,2,3"); + } + public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider { public override HashSet GetCustomTypes() => new HashSet(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) }; diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs index 212df6109..41fa5d18b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionHelperTests.cs @@ -1,256 +1,275 @@ using FluentAssertions; using NFluent; +using System.Collections; using System.Linq.Dynamic.Core.Parser; using System.Linq.Expressions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests.Parser +namespace System.Linq.Dynamic.Core.Tests.Parser; + +public class ExpressionHelperTests { - public class ExpressionHelperTests + private readonly ExpressionHelper _sut; + + public ExpressionHelperTests() { - private readonly ExpressionHelper _expressionHelper; + _sut = new ExpressionHelper(ParsingConfig.Default); + } - public ExpressionHelperTests() + [Fact] + public void ExpressionHelper_WrapConstantExpression_false() + { + // Assign + var config = new ParsingConfig { - _expressionHelper = new ExpressionHelper(ParsingConfig.Default); - } + UseParameterizedNamesInDynamicQuery = false + }; + var expressionHelper = new ExpressionHelper(config); - [Fact] - public void ExpressionHelper_WrapConstantExpression_false() - { - // Assign - var config = new ParsingConfig - { - UseParameterizedNamesInDynamicQuery = false - }; - var expressionHelper = new ExpressionHelper(config); - - string value = "test"; - Expression expression = Expression.Constant(value); - - // Act - expressionHelper.WrapConstantExpression(ref expression); - - // Assert - Check.That(expression).IsInstanceOf(); - Check.That(expression.ToString()).Equals("\"test\""); - } - - [Fact] - public void ExpressionHelper_WrapNullableConstantExpression_false() - { - // Assign - var config = new ParsingConfig - { - UseParameterizedNamesInDynamicQuery = false - }; - var expressionHelper = new ExpressionHelper(config); - - int? value = 42; - Expression expression = Expression.Constant(value); - - // Act - expressionHelper.WrapConstantExpression(ref expression); - - // Assert - Check.That(expression).IsInstanceOf(); - Check.That(expression.ToString()).Equals("42"); - } - - [Fact] - public void ExpressionHelper_WrapConstantExpression_true() - { - // Assign - var config = new ParsingConfig - { - UseParameterizedNamesInDynamicQuery = true - }; - var expressionHelper = new ExpressionHelper(config); - - string value = "test"; - Expression expression = Expression.Constant(value); - - // Act - expressionHelper.WrapConstantExpression(ref expression); - expressionHelper.WrapConstantExpression(ref expression); - - // Assert - Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); - Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.String]).Value"); - } - - [Fact] - public void ExpressionHelper_WrapNullableConstantExpression_true() - { - // Assign - var config = new ParsingConfig - { - UseParameterizedNamesInDynamicQuery = true - }; - var expressionHelper = new ExpressionHelper(config); - - int? value = 42; - Expression expression = Expression.Constant(value); - - // Act - expressionHelper.WrapConstantExpression(ref expression); - expressionHelper.WrapConstantExpression(ref expression); - - // Assert - Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); - Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.Int32]).Value"); - } - - [Fact] - public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid() - { - // Assign - string guidAsString = Guid.NewGuid().ToString(); + string value = "test"; + Expression expression = Expression.Constant(value); - // Act - Expression result = _expressionHelper.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + // Act + expressionHelper.WrapConstantExpression(ref expression); - // Assert - Check.That(result).IsInstanceOf(); - Check.That(result.ToString()).Equals(guidAsString); - } + // Assert + Check.That(expression).IsInstanceOf(); + Check.That(expression.ToString()).Equals("\"test\""); + } - [Fact] - public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid_Invalid() + [Fact] + public void ExpressionHelper_WrapNullableConstantExpression_false() + { + // Assign + var config = new ParsingConfig { - // Assign - string guidAsString = "x"; + UseParameterizedNamesInDynamicQuery = false + }; + var expressionHelper = new ExpressionHelper(config); - // Act - Expression result = _expressionHelper.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); + int? value = 42; + Expression expression = Expression.Constant(value); - // Assert - Check.That(result).IsNull(); - } + // Act + expressionHelper.WrapConstantExpression(ref expression); + + // Assert + Check.That(expression).IsInstanceOf(); + Check.That(expression.ToString()).Equals("42"); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNullable() + [Fact] + public void ExpressionHelper_WrapConstantExpression_true() + { + // Assign + var config = new ParsingConfig { - // Assign - Expression> expression = x => x.Relation1.Relation2.Id; + UseParameterizedNamesInDynamicQuery = true + }; + var expressionHelper = new ExpressionHelper(config); + + string value = "test"; + Expression expression = Expression.Constant(value); - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + // Act + expressionHelper.WrapConstantExpression(ref expression); + expressionHelper.WrapConstantExpression(ref expression); - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); - } + // Assert + Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); + Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.String]).Value"); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNullable_Config_Has_UseDefault() + [Fact] + public void ExpressionHelper_WrapNullableConstantExpression_true() + { + // Assign + var config = new ParsingConfig { - // Assign - var config = new ParsingConfig - { - NullPropagatingUseDefaultValueForNonNullableValueTypes = true - }; - var expressionHelper = new ExpressionHelper(config); + UseParameterizedNamesInDynamicQuery = true + }; + var expressionHelper = new ExpressionHelper(config); - Expression> expression = x => x.Relation1.Relation2.Id; + int? value = 42; + Expression expression = Expression.Constant(value); - // Act - bool result = expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + // Act + expressionHelper.WrapConstantExpression(ref expression); + expressionHelper.WrapConstantExpression(ref expression); - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); - } + // Assert + Check.That(expression.GetType().FullName).Equals("System.Linq.Expressions.PropertyExpression"); + Check.That(expression.ToString()).Equals("value(System.Linq.Dynamic.Core.Parser.WrappedValue`1[System.Int32]).Value"); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1NullableInt() - { - // Assign - Expression> expression = x => x.IdNullable; + [Fact] + public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid() + { + // Assign + string guidAsString = Guid.NewGuid().ToString(); - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + // Act + Expression result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.IdNullable != null))"); - } + // Assert + Check.That(result).IsInstanceOf(); + Check.That(result.ToString()).Equals(guidAsString); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1NullableString() - { - // Assign - Expression> expression = x => x.S; + [Fact] + public void ExpressionHelper_OptimizeStringForEqualityIfPossible_Guid_Invalid() + { + // Assign + string guidAsString = "x"; - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + // Act + Expression result = _sut.OptimizeStringForEqualityIfPossible(guidAsString, typeof(Guid)); - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.S != null))"); - } + // Assert + Check.That(result).IsNull(); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable_AddSelfFalse() - { - // Assign - Expression> expression = x => x.Relation1.Relation2.IdNullable; + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNullable() + { + // Assign + Expression> expression = x => x.Relation1.Relation2.Id; - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, false, out Expression generatedExpression); + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("(((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null))"); - } + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); + } - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable_AddSelfTrue() + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3NonNullable_Config_Has_UseDefault() + { + // Assign + var config = new ParsingConfig { - // Assign - Expression> expression = x => x.Relation1.Relation2.IdNullable; + NullPropagatingUseDefaultValueForNonNullableValueTypes = true + }; + var expressionHelper = new ExpressionHelper(config); - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + Expression> expression = x => x.Relation1.Relation2.Id; - // Assert - Check.That(result).IsTrue(); - Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.IdNullable != null))"); - } + // Act + bool result = expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); - [Fact] - public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nullable() - { - // Assign - Expression> expression = x => x.Id; + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.Id != null))"); + } - // Act - bool result = _expressionHelper.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1NullableInt() + { + // Assign + Expression> expression = x => x.IdNullable; - // Assert - result.Should().BeTrue(); - generatedExpression.ToString().Should().StartWith("((x != null) AndAlso (x =>").And.EndWith("!= null))"); - } + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); - class Item - { - public int Id { get; set; } - public Relation1 Relation1 { get; set; } - } + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.IdNullable != null))"); + } - class Relation1 - { - public int Id { get; set; } - public Relation2 Relation2 { get; set; } - } + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested1NullableString() + { + // Assign + Expression> expression = x => x.S; - class Relation2 - { - public int Id { get; set; } + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("((x != null) AndAlso (x => x.S != null))"); + } + + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable_AddSelfFalse() + { + // Assign + Expression> expression = x => x.Relation1.Relation2.IdNullable; + + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, false, out Expression generatedExpression); + + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("(((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null))"); + } + + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nested3Nullable_AddSelfTrue() + { + // Assign + Expression> expression = x => x.Relation1.Relation2.IdNullable; + + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + + // Assert + Check.That(result).IsTrue(); + Check.That(generatedExpression.ToString()).IsEqualTo("((((x != null) AndAlso (x.Relation1 != null)) AndAlso (x.Relation1.Relation2 != null)) AndAlso (x => x.Relation1.Relation2.IdNullable != null))"); + } + + [Fact] + public void ExpressionHelper_TryGenerateAndAlsoNotNullExpression_Nullable() + { + // Assign + Expression> expression = x => x.Id; + + // Act + bool result = _sut.TryGenerateAndAlsoNotNullExpression(expression, true, out Expression generatedExpression); + + // Assert + result.Should().BeTrue(); + generatedExpression.ToString().Should().StartWith("((x != null) AndAlso (x =>").And.EndWith("!= null))"); + } + + [Fact] + public void ConvertAnyArrayToObjectArray_ShouldConvertIntArrayToObjectArray() + { + // Arrange + var array = new[] { 1, 2, 3 }; + var arrayExpression = Expression.Constant(array); + + // Act + var expression = _sut.ConvertAnyArrayToObjectArray(arrayExpression); + + // Assert + expression.Should().NotBeNull(); + + var lambdaExpressionCompiled = Expression.Lambda(expression).Compile(); + var result = (object[]) lambdaExpressionCompiled.DynamicInvoke(); + + result.Should().HaveCount(array.Length).And.ContainInOrder((object) 1, (object) 2, (object) 3); + } + + class Item + { + public int Id { get; set; } + public Relation1 Relation1 { get; set; } + } + + class Relation1 + { + public int Id { get; set; } + public Relation2 Relation2 { get; set; } + } + + class Relation2 + { + public int Id { get; set; } - public int? IdNullable { get; set; } + public int? IdNullable { get; set; } - public string S { get; set; } - } + public string S { get; set; } } -} +} \ No newline at end of file From 06d4110a0cf7413fc42e7f123d9198f0cc085d4b Mon Sep 17 00:00:00 2001 From: Abbas Cyclewala Date: Mon, 14 Aug 2023 00:10:25 +0530 Subject: [PATCH 002/153] added symbol check when checking for identifier (#728) * added symbol check * added test case for preference * simplified code for type prioritization --- .../Parser/ExpressionParser.cs | 16 +++++++++++++--- .../Parser/ExpressionParserTests.cs | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 3d3768224..279e28d15 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -931,10 +931,20 @@ private Expression ParseIdentifier() var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); - var extraCondition = !_parsingConfig.PrioritizePropertyOrFieldOverTheType || - (_parsingConfig.PrioritizePropertyOrFieldOverTheType && !(value is Type && _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null)); + + bool shouldPrioritizeType = true; + + if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type) + { + bool isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null; + bool hasSymbol = _symbols.ContainsKey(_textParser.CurrentToken.Text); + if (isPropertyOrField || hasSymbol) + { + shouldPrioritizeType = false; + } + } - if (isValidKeyWord && extraCondition) + if (isValidKeyWord && shouldPrioritizeType) { if (value is Type typeValue) { diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs index 4e866a7a5..b963c8e30 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.cs @@ -331,12 +331,14 @@ public void Parse_NullableShouldReturnNullable(string expression, object resultT [InlineData("Company.Equals(null, null)", "Equals(null, null)")] [InlineData("MainCompany.Name", "company.MainCompany.Name")] [InlineData("Name", "company.Name")] + [InlineData("company.Name","company.Name")] [InlineData("DateTime", "company.DateTime")] public void Parse_When_PrioritizePropertyOrFieldOverTheType_IsTrue(string expression, string result) { // Arrange var config = new ParsingConfig { + IsCaseSensitive = true, CustomTypeProvider = _dynamicTypeProviderMock.Object }; ParameterExpression[] parameters = { ParameterExpressionHelper.CreateParameterExpression(typeof(Company), "company") }; From 06d70651be40c8affcadfc78ca22607d3f43653d Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 23 Aug 2023 19:04:00 +0200 Subject: [PATCH 003/153] Fix AddSignatures and SubtractSignatures (#739) * Fix AddSignatures and SubtractSignatures * fix test --- .../Parser/ExpressionParser.cs | 10 +- .../IAddAndSubtractSignatures.cs | 38 ----- .../SupportedOperands/IAddSignatures.cs | 20 +++ .../SupportedOperands/ISubtractSignatures.cs | 12 ++ .../ExpressionTests.AddAndSubstract.cs | 140 ++++++++++++++++++ .../ExpressionTests.cs | 29 ---- 6 files changed, 177 insertions(+), 72 deletions(-) delete mode 100644 src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs create mode 100644 src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs create mode 100644 src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/ExpressionTests.AddAndSubstract.cs diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 279e28d15..c16ec5bc0 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -689,13 +689,13 @@ private Expression ParseShiftOperator() // +, - operators private Expression ParseAdditive() { - Expression left = ParseMultiplicative(); + Expression left = ParseArithmetic(); while (_textParser.CurrentToken.Id is TokenId.Plus or TokenId.Minus) { Token op = _textParser.CurrentToken; _textParser.NextToken(); - Expression right = ParseMultiplicative(); + Expression right = ParseArithmetic(); switch (op.Id) { case TokenId.Plus: @@ -705,13 +705,13 @@ private Expression ParseAdditive() } else { - CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + CheckAndPromoteOperands(typeof(IAddSignatures), op.Id, op.Text, ref left, ref right, op.Pos); left = _expressionHelper.GenerateAdd(left, right); } break; case TokenId.Minus: - CheckAndPromoteOperands(typeof(IAddAndSubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos); + CheckAndPromoteOperands(typeof(ISubtractSignatures), op.Id, op.Text, ref left, ref right, op.Pos); left = _expressionHelper.GenerateSubtract(left, right); break; } @@ -720,7 +720,7 @@ private Expression ParseAdditive() } // *, /, %, mod operators - private Expression ParseMultiplicative() + private Expression ParseArithmetic() { Expression left = ParseUnary(); while (_textParser.CurrentToken.Id is TokenId.Asterisk or TokenId.Slash or TokenId.Percent || TokenIdentifierIs("mod")) diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs deleted file mode 100644 index a329e9614..000000000 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddAndSubtractSignatures.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace System.Linq.Dynamic.Core.Parser.SupportedOperands; - -internal interface IAddAndSubtractSignatures : IArithmeticSignatures -{ - void F(TimeSpan x, TimeSpan y); - - void F(TimeSpan x, TimeSpan? y); - - void F(TimeSpan? x, TimeSpan y); - - void F(TimeSpan? x, TimeSpan? y); - - void F(DateTime x, DateTime y); - - void F(DateTime x, DateTime? y); - - void F(DateTime? x, DateTime y); - - void F(DateTime? x, DateTime? y); - -#if NET6_0_OR_GREATER - void F(DateOnly x, DateOnly y); - - void F(DateOnly x, DateOnly? y); - - void F(DateOnly? x, DateOnly y); - - void F(DateOnly? x, DateOnly? y); - - void F(TimeOnly x, TimeOnly y); - - void F(TimeOnly x, TimeOnly? y); - - void F(TimeOnly? x, TimeOnly y); - - void F(TimeOnly? x, TimeOnly? y); -#endif -} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs new file mode 100644 index 000000000..15c398f99 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/IAddSignatures.cs @@ -0,0 +1,20 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands; + +internal interface IAddSignatures : IArithmeticSignatures +{ + void F(TimeSpan x, TimeSpan y); + + void F(TimeSpan x, TimeSpan? y); + + void F(TimeSpan? x, TimeSpan y); + + void F(TimeSpan? x, TimeSpan? y); + + void F(DateTime x, TimeSpan y); + + void F(DateTime x, TimeSpan? y); + + void F(DateTime? x, TimeSpan y); + + void F(DateTime? x, TimeSpan? y); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs new file mode 100644 index 000000000..c46b22ce2 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedOperands/ISubtractSignatures.cs @@ -0,0 +1,12 @@ +namespace System.Linq.Dynamic.Core.Parser.SupportedOperands; + +internal interface ISubtractSignatures : IAddSignatures +{ + void F(DateTime x, DateTime y); + + void F(DateTime x, DateTime? y); + + void F(DateTime? x, DateTime y); + + void F(DateTime? x, DateTime? y); +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.AddAndSubstract.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.AddAndSubstract.cs new file mode 100644 index 000000000..6d8081bdf --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.AddAndSubstract.cs @@ -0,0 +1,140 @@ +using FluentAssertions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests; + +public partial class ExpressionTests +{ + [Fact] + public void ExpressionTests_Add_Number() + { + // Arrange + var values = new[] { -1, 2 }.AsQueryable(); + + // Act + var result = values.Select("it + 1"); + var expected = values.Select(i => i + 1); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_Add_String() + { + // Arrange + var values = new[] { "a", "b" }.AsQueryable(); + + // Act + var result = values.Select("it + \"z\""); + var expected = values.Select(i => i + "z"); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_SupportedOperands_IAddAndSubtractSignatures_DateTime_Subtracts_DateTime() + { + // Arrange + var values = new[] + { + new + { + Value1 = new DateTime(2023, 1, 4), + Value2 = new DateTime(2023, 1, 3) + } + }.AsQueryable(); + + // Act + var result = values.Select("Value1 - Value2"); + var expected = values.Select(x => x.Value1 - x.Value2); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_SupportedOperands_IAddAndSubtractSignatures_DateTime_Adds_TimeSpan() + { + // Arrange + var values = new[] + { + new + { + Value1 = new DateTime(2023, 1, 4), + Value2 = TimeSpan.FromDays(1) + } + }.AsQueryable(); + + // Act + var result = values.Select("Value1 + Value2"); + var expected = values.Select(x => x.Value1 + x.Value2); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_SupportedOperands_IAddAndSubtractSignatures_DateTime_Subtracts_TimeSpan() + { + // Arrange + var values = new[] + { + new + { + Value1 = new DateTime(2023, 1, 4), + Value2 = TimeSpan.FromDays(1) + } + }.AsQueryable(); + + // Act + var result = values.Select("Value1 - Value2"); + var expected = values.Select(x => x.Value1 - x.Value2); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_SupportedOperands_IAddAndSubtractSignatures_TimeSpan_Adds_TimeSpan() + { + // Arrange + var values = new[] + { + new + { + Value1 = TimeSpan.FromDays(1), + Value2 = TimeSpan.FromDays(1) + } + }.AsQueryable(); + + // Act + var result = values.Select("Value1 + Value2"); + var expected = values.Select(x => x.Value1 + x.Value2); + + // Assert + result.Should().Contain(expected); + } + + [Fact] + public void ExpressionTests_SupportedOperands_IAddAndSubtractSignatures_TimeSpan_Subtracts_TimeSpan() + { + // Arrange + var values = new[] + { + new + { + Value1 = TimeSpan.FromDays(7), + Value2 = TimeSpan.FromDays(1) + } + }.AsQueryable(); + + // Act + var result = values.Select("Value1 - Value2"); + var expected = values.Select(x => x.Value1 - x.Value2); + + // Assert + result.Should().Contain(expected); + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs index 5d7e7a9c7..9846cf88e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.cs @@ -85,35 +85,6 @@ public class TargetClass public TestEnum A { get; set; } } - [Fact] - public void ExpressionTests_Add_Number() - { - // Arrange - var values = new[] { -1, 2 }.AsQueryable(); - - // Act - var result = values.Select("it + 1"); - var expected = values.Select(i => i + 1); - - // Assert - Check.That(result).ContainsExactly(expected); - } - - [Fact] - public void ExpressionTests_Add_String() - { - // Arrange - var values = new[] { "a", "b" }.AsQueryable(); - - // Act - var result = values.Select("it + \"z\""); - var expected = values.Select(i => i + "z"); - - // Assert - Check.That(result).ContainsExactly(expected); - } - - [Fact] public void ExpressionTests_AndAlso() { From d5122c9995a9b67799f89ca46a6d296d3019b864 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 10 Sep 2023 10:40:03 +0200 Subject: [PATCH 004/153] Add support for out keyword (#743) * wip * ok! * . * . * res * tests * ... * func --- .../DynamicQueryableExtensions.cs | 11 +- .../Parser/ExpressionParser.cs | 111 ++++++++++-- .../Parser/SupportedMethods/MethodFinder.cs | 28 ++- src/System.Linq.Dynamic.Core/Res.cs | 160 +++++++++--------- .../Tokenizer/TextParser.cs | 2 +- .../ExpressionTests.MethodCall.cs | 64 +++++++ .../Helpers/Models/User.cs | 110 +++++++----- .../Parser/MethodFinderTest.cs | 23 ++- .../System.Linq.Dynamic.Core.Tests.csproj | 1 + 9 files changed, 350 insertions(+), 160 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs index 8908434dd..fc1268654 100644 --- a/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs +++ b/src/System.Linq.Dynamic.Core/DynamicQueryableExtensions.cs @@ -1802,10 +1802,15 @@ public static IQueryable Select(this IQueryable source, Parsin bool createParameterCtor = config.EvaluateGroupByAtDatabase || SupportsLinqToObjects(config, source); LambdaExpression lambda = DynamicExpressionParser.ParseLambda(config, createParameterCtor, source.ElementType, typeof(TResult), selector, args); - var optimized = OptimizeExpression(Expression.Call( - typeof(Queryable), nameof(Queryable.Select), + var methodCallExpression = Expression.Call( + typeof(Queryable), + nameof(Queryable.Select), new[] { source.ElementType, typeof(TResult) }, - source.Expression, Expression.Quote(lambda))); + source.Expression, + Expression.Quote(lambda) + ); + + var optimized = OptimizeExpression(methodCallExpression); return source.Provider.CreateQuery(optimized); } diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index c16ec5bc0..ada4b2b95 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -20,6 +20,9 @@ namespace System.Linq.Dynamic.Core.Parser; /// public class ExpressionParser { + private static readonly string[] OutKeywords = { "out", "$out" }; + private const string DiscardVariable = "_"; + private const string MethodOrderBy = nameof(Queryable.OrderBy); private const string MethodOrderByDescending = nameof(Queryable.OrderByDescending); private const string MethodThenBy = nameof(Queryable.ThenBy); @@ -165,7 +168,31 @@ public Expression Parse(Type? resultType, bool createParameterCtor = true) return expr; } -#pragma warning disable 0219 + // out keyword + private Expression ParseOutKeyword() + { + if (_textParser.CurrentToken.Id == TokenId.Identifier && OutKeywords.Contains(_textParser.CurrentToken.Text)) + { + // Go to next token (which should be a '_') + _textParser.NextToken(); + + var variableName = _textParser.CurrentToken.Text; + if (variableName != DiscardVariable) + { + throw ParseError(_textParser.CurrentToken.Pos, Res.OutKeywordRequiresDiscard); + } + + // Advance to next token + _textParser.NextToken(); + + // Use MakeByRefType() to indicate that it's a by-reference type because C# uses this for both 'ref' and 'out' parameters. + // The "typeof(object).MakeByRefType()" is used, this will be changed later in the flow to the real type. + return Expression.Parameter(typeof(object).MakeByRefType(), variableName); + } + + return ParseConditionalOperator(); + } + internal IList ParseOrdering(bool forceThenBy = false) { var orderings = new List(); @@ -206,7 +233,6 @@ internal IList ParseOrdering(bool forceThenBy = false) _textParser.ValidateToken(TokenId.End, Res.SyntaxError); return orderings; } -#pragma warning restore 0219 // ?: operator private Expression ParseConditionalOperator() @@ -931,9 +957,9 @@ private Expression ParseIdentifier() var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); - + bool shouldPrioritizeType = true; - + if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type) { bool isPropertyOrField = _it != null && FindPropertyOrField(_it.Type, _textParser.CurrentToken.Text, false) != null; @@ -1774,16 +1800,19 @@ private Expression ParseMemberAccess(Type? type, Expression? expression) throw ParseError(errorPos, Res.MethodsAreInaccessible, TypeHelper.GetTypeName(method.DeclaringType!)); } - if (method.IsGenericMethod) + MethodInfo methodToCall; + if (!method.IsGenericMethod) + { + methodToCall = method; + } + else { var genericParameters = method.GetParameters().Where(p => p.ParameterType.IsGenericParameter); var typeArguments = genericParameters.Select(a => args[a.Position].Type); - var constructedMethod = method.MakeGenericMethod(typeArguments.ToArray()); - - return Expression.Call(expression, constructedMethod, args); + methodToCall = method.MakeGenericMethod(typeArguments.ToArray()); } - - return Expression.Call(expression, method, args); + + return CallMethod(expression, methodToCall, args); default: throw ParseError(errorPos, Res.AmbiguousMethodInvocation, id, TypeHelper.GetTypeName(type)); @@ -1848,6 +1877,59 @@ private Expression ParseMemberAccess(Type? type, Expression? expression) throw ParseError(errorPos, Res.UnknownPropertyOrField, id, TypeHelper.GetTypeName(type)); } + private static Expression CallMethod(Expression? expression, MethodInfo methodToCall, Expression[] args) + { +#if NET35 + return Expression.Call(expression, methodToCall, args); +#else + if (!args.OfType().Any(p => p.IsByRef)) + { + return Expression.Call(expression, methodToCall, args); + } + + // A list which is used to store all method arguments. + var newList = new List(); + + // A list which contains the variable expression for the 'out' parameter, and also contains the returnValue variable. + var blockList = new List(); + + foreach (var arg in args) + { + if (arg is ParameterExpression { IsByRef: true } parameterExpression) + { + // Create a variable expression to hold the 'out' parameter. + var variable = Expression.Variable(parameterExpression.Type, parameterExpression.Name); + + newList.Add(variable); + blockList.Add(variable); + } + else + { + newList.Add(arg); + } + } + + // Create a method call expression to call the method + var methodCall = Expression.Call(expression, methodToCall, newList); + + // Create a variable to hold the return value + var returnValue = Expression.Variable(methodToCall.ReturnType); + + // Add this return variable to the blockList + blockList.Add(returnValue); + + // Create the block to return the boolean value. + var block = Expression.Block( + blockList.ToArray(), + Expression.Assign(returnValue, methodCall), + returnValue + ); + + // Create the lambda expression (note that expression must be a ParameterExpression). + return Expression.Lambda(block, (ParameterExpression)expression!); +#endif + } + private Expression ParseAsLambda(string id) { // This might be an internal variable for use within a lambda expression, so store it as such @@ -2021,7 +2103,7 @@ private Expression ParseEnumerable(Expression instance, Type elementType, string private Type ResolveTypeFromArgumentExpression(string functionName, Expression argumentExpression, int? arguments = null) { - string argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second "; + var argument = arguments == null ? string.Empty : arguments == 1 ? "first " : "second "; switch (argumentExpression) { @@ -2088,7 +2170,7 @@ private Expression[] ParseArguments() var argList = new List(); while (true) { - var argumentExpression = ParseConditionalOperator(); + var argumentExpression = ParseOutKeyword(); _expressionHelper.WrapConstantExpression(ref argumentExpression); @@ -2102,6 +2184,11 @@ private Expression[] ParseArguments() _textParser.NextToken(); } + //if (argList.OfType().Count() > 1) + //{ + // throw ParseError(_textParser.CurrentToken.Pos, Res.OutVariableSingleRequired); + //} + return argList.ToArray(); } diff --git a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs index 3a926a2e3..d5d667b97 100644 --- a/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs +++ b/src/System.Linq.Dynamic.Core/Parser/SupportedMethods/MethodFinder.cs @@ -170,7 +170,7 @@ public int FindIndexer(Type type, Expression[] args, out MethodBase? method) return 0; } - bool IsApplicable(MethodData method, Expression[] args) + private bool IsApplicable(MethodData method, Expression[] args) { bool isParamArray = method.Parameters.Length > 0 && method.Parameters.Last().IsDefined(typeof(ParamArrayAttribute), false); @@ -221,18 +221,30 @@ bool IsApplicable(MethodData method, Expression[] args) } else { - ParameterInfo pi = method.Parameters[i]; - if (pi.IsOut) + var methodParameter = method.Parameters[i]; + if (methodParameter.IsOut && args[i] is ParameterExpression parameterExpression) { +#if NET35 return false; - } +#else + if (!parameterExpression.IsByRef) + { + return false; + } - var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], pi.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); - if (promotedExpression == null) + promotedArgs[i] = Expression.Parameter(methodParameter.ParameterType, methodParameter.Name); +#endif + } + else { - return false; + var promotedExpression = _parsingConfig.ExpressionPromoter.Promote(args[i], methodParameter.ParameterType, false, method.MethodBase.DeclaringType != typeof(IEnumerableSignatures)); + if (promotedExpression == null) + { + return false; + } + + promotedArgs[i] = promotedExpression; } - promotedArgs[i] = promotedExpression; } } diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs index 2402f710b..0b897a856 100644 --- a/src/System.Linq.Dynamic.Core/Res.cs +++ b/src/System.Linq.Dynamic.Core/Res.cs @@ -1,81 +1,81 @@ -namespace System.Linq.Dynamic.Core +namespace System.Linq.Dynamic.Core; + +internal static class Res { - internal static class Res - { - public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor"; - public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'"; - public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'"; - public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression"; - public const string BinraryCharExpected = "Binary character expected"; - public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other"; - public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'"; - public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported"; - public const string CloseBracketExpected = "']' expected"; - public const string CloseBracketOrCommaExpected = "']' or ',' expected"; - public const string CloseParenOrCommaExpected = "')' or ',' expected"; - public const string CloseParenOrOperatorExpected = "')' or operator expected"; - public const string ColonExpected = "':' expected"; - public const string DigitExpected = "Digit expected"; - public const string DotExpected = "'.' expected"; - public const string DotOrOpenParenExpected = "'.' or '(' expected"; - public const string DotOrOpenParenOrStringLiteralExpected = "'.' or '(' or string literal expected"; - public const string DynamicExpandoObjectIsNotSupported = "Dynamic / ExpandoObject is not supported in .NET 3.5, UAP and .NETStandard 1.3"; - public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once"; - public const string EnumTypeNotFound = "Enum type '{0}' not found"; - public const string EnumValueExpected = "Enum value expected"; - public const string EnumValueNotDefined = "Enum value '{0}' is not defined in enum type '{1}'"; - public const string ExpressionExpected = "Expression expected"; - public const string ExpressionTypeMismatch = "Expression of type '{0}' expected"; - public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'"; - public const string FunctionRequiresOneArg = "The '{0}' function requires one argument"; - public const string FunctionRequiresOneNotNullArg = "The '{0}' function requires one argument which is not null."; - public const string FunctionRequiresNotNullArgOfType = "The '{0}' function requires the {1}argument to be not null and of type {2}."; - public const string FunctionRequiresOneOrTwoArgs = "The '{0}' function requires 1 or 2 arguments"; - public const string HexCharExpected = "Hexadecimal character expected"; - public const string IQueryableProviderNotAsync = "The provider for the source IQueryable doesn't implement IAsyncQueryProvider/IDbAsyncQueryProvider. Only providers that implement IAsyncQueryProvider/IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations."; - public const string IdentifierExpected = "Identifier expected"; - public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected"; - public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; - public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; - public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; - public const string IncompatibleTypes = "Types '{0}' and '{1}' are incompatible"; - public const string InvalidBinaryIntegerLiteral = "Invalid binary integer literal '{0}'"; - public const string InvalidCharacter = "Syntax error '{0}'"; - public const string InvalidCharacterLiteral = "Character literal must contain exactly one character"; - public const string InvalidIndex = "Array index must be an integer expression"; - public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; - public const string InvalidIntegerQualifier = "Invalid integer literal qualifier '{0}'"; - public const string InvalidRealLiteral = "Invalid real literal '{0}'"; - public const string InvalidStringQuoteCharacter = "An escaped string should start with a double (\") or a single (') quote."; - public const string InvalidStringLength = "String '{0}' should have at least {1} characters."; - public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments"; - public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; - public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; - public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers."; - public const string MissingAsClause = "Expression is missing an 'as' clause"; - public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; - public const string NoApplicableAggregate = "No applicable aggregate method '{0}({1})' exists"; - public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; - public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; - public const string NoItInScope = "No 'it' is in scope"; - public const string NoMatchingConstructor = "No matching constructor in type '{0}'"; - public const string NoParentInScope = "No 'parent' is in scope"; - public const string NoRootInScope = "No 'root' is in scope"; - public const string NullPropagationRequiresCorrectArgs = "The 'np' (null-propagation) function requires 1 or 2 arguments"; - public const string NullPropagationRequiresValidExpression = "The 'np' (null-propagation) function requires the first argument to be a MemberExpression, ParameterExpression or MethodCallExpression"; - public const string OpenBracketExpected = "'[' expected"; - public const string OpenCurlyParenExpected = "'{' expected"; - public const string OpenParenExpected = "'(' expected"; - public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected"; - public const string ParseExceptionFormat = "{0} (at index {1})"; - public const string SyntaxError = "Syntax error"; - public const string TokenExpected = "{0} expected"; - public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form"; - public const string TypeNotFound = "Type '{0}' not found"; - public const string UnexpectedUnclosedString = "Unexpected end of string with unclosed string at position {0} near '{1}'."; - public const string UnexpectedUnrecognizedEscapeSequence = "Unexpected unrecognized escape sequence at position {0} near '{1}'."; - public const string UnknownIdentifier = "Unknown identifier '{0}'"; - public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'"; - public const string UnterminatedStringLiteral = "Unterminated string literal"; - } -} + public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor"; + public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'"; + public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'"; + public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression"; + public const string BinaryCharExpected = "Binary character expected"; + public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other"; + public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'"; + public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported"; + public const string CloseBracketExpected = "']' expected"; + public const string CloseBracketOrCommaExpected = "']' or ',' expected"; + public const string CloseParenOrCommaExpected = "')' or ',' expected"; + public const string CloseParenOrOperatorExpected = "')' or operator expected"; + public const string ColonExpected = "':' expected"; + public const string DigitExpected = "Digit expected"; + public const string DotExpected = "'.' expected"; + public const string DotOrOpenParenExpected = "'.' or '(' expected"; + public const string DotOrOpenParenOrStringLiteralExpected = "'.' or '(' or string literal expected"; + public const string DynamicExpandoObjectIsNotSupported = "Dynamic / ExpandoObject is not supported in .NET 3.5, UAP and .NETStandard 1.3"; + public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once"; + public const string EnumTypeNotFound = "Enum type '{0}' not found"; + public const string EnumValueExpected = "Enum value expected"; + public const string EnumValueNotDefined = "Enum value '{0}' is not defined in enum type '{1}'"; + public const string ExpressionExpected = "Expression expected"; + public const string ExpressionTypeMismatch = "Expression of type '{0}' expected"; + public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'"; + public const string FunctionRequiresOneArg = "The '{0}' function requires one argument"; + public const string FunctionRequiresOneNotNullArg = "The '{0}' function requires one argument which is not null."; + public const string FunctionRequiresNotNullArgOfType = "The '{0}' function requires the {1}argument to be not null and of type {2}."; + public const string FunctionRequiresOneOrTwoArgs = "The '{0}' function requires 1 or 2 arguments"; + public const string HexCharExpected = "Hexadecimal character expected"; + public const string IQueryableProviderNotAsync = "The provider for the source IQueryable doesn't implement IAsyncQueryProvider/IDbAsyncQueryProvider. Only providers that implement IAsyncQueryProvider/IDbAsyncQueryProvider can be used for Entity Framework asynchronous operations."; + public const string IdentifierExpected = "Identifier expected"; + public const string IdentifierImplementingInterfaceExpected = "Identifier implementing interface '{0}' expected"; + public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments"; + public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'"; + public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'"; + public const string IncompatibleTypes = "Types '{0}' and '{1}' are incompatible"; + public const string InvalidBinaryIntegerLiteral = "Invalid binary integer literal '{0}'"; + public const string InvalidCharacter = "Syntax error '{0}'"; + public const string InvalidCharacterLiteral = "Character literal must contain exactly one character"; + public const string InvalidIndex = "Array index must be an integer expression"; + public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'"; + public const string InvalidIntegerQualifier = "Invalid integer literal qualifier '{0}'"; + public const string InvalidRealLiteral = "Invalid real literal '{0}'"; + public const string InvalidStringQuoteCharacter = "An escaped string should start with a double (\") or a single (') quote."; + public const string InvalidStringLength = "String '{0}' should have at least {1} characters."; + public const string IsNullRequiresTwoArgs = "The 'isnull' function requires two arguments"; + public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value"; + public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible"; + public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers."; + public const string MissingAsClause = "Expression is missing an 'as' clause"; + public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; + public const string NoApplicableAggregate = "No applicable aggregate method '{0}({1})' exists"; + public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; + public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; + public const string NoItInScope = "No 'it' is in scope"; + public const string NoMatchingConstructor = "No matching constructor in type '{0}'"; + public const string NoParentInScope = "No 'parent' is in scope"; + public const string NoRootInScope = "No 'root' is in scope"; + public const string NullPropagationRequiresCorrectArgs = "The 'np' (null-propagation) function requires 1 or 2 arguments"; + public const string NullPropagationRequiresValidExpression = "The 'np' (null-propagation) function requires the first argument to be a MemberExpression, ParameterExpression or MethodCallExpression"; + public const string OpenBracketExpected = "'[' expected"; + public const string OpenCurlyParenExpected = "'{' expected"; + public const string OpenParenExpected = "'(' expected"; + public const string OpenParenOrIdentifierExpected = "'(' or Identifier expected"; + public const string OutKeywordRequiresDiscard = "When using an out variable, a discard '_' is required."; + public const string ParseExceptionFormat = "{0} (at index {1})"; + public const string SyntaxError = "Syntax error"; + public const string TokenExpected = "{0} expected"; + public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form"; + public const string TypeNotFound = "Type '{0}' not found"; + public const string UnexpectedUnclosedString = "Unexpected end of string with unclosed string at position {0} near '{1}'."; + public const string UnexpectedUnrecognizedEscapeSequence = "Unexpected unrecognized escape sequence at position {0} near '{1}'."; + public const string UnknownIdentifier = "Unknown identifier '{0}'"; + public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'"; + public const string UnterminatedStringLiteral = "Unterminated string literal"; +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs index 82284d7a3..7369a3ddf 100644 --- a/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs +++ b/src/System.Linq.Dynamic.Core/Tokenizer/TextParser.cs @@ -495,7 +495,7 @@ private void ValidateBinaryChar() { if (!IsZeroOrOne(_ch)) { - throw ParseError(_textPos, Res.BinraryCharExpected); + throw ParseError(_textPos, Res.BinaryCharExpected); } } diff --git a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.MethodCall.cs b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.MethodCall.cs index b858dc5c8..0c51ea52e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.MethodCall.cs +++ b/test/System.Linq.Dynamic.Core.Tests/ExpressionTests.MethodCall.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Tests.Helpers.Models; using FluentAssertions; using Moq; @@ -33,6 +34,69 @@ private static ParsingConfig CreateParsingConfigForStaticMethodCallTests() }; } + [Fact] + public void ExpressionTests_MethodCall_WithArgument_And_1_OutArgument() + { + // Arrange + var config = CreateParsingConfigForMethodCallTests(); + var users = User.GenerateSampleModels(5); + + // Act + string s = ""; + var expected = users.Select(u => u.TryParseWithArgument(u.UserName, out s)); + var result = users.AsQueryable().Select(config, "TryParseWithArgument(it.UserName, $out _)"); + + // Assert + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public void ExpressionTests_MethodCall_WithArgument_And_2_OutArguments() + { + // Arrange + var config = CreateParsingConfigForMethodCallTests(); + var users = User.GenerateSampleModels(5); + + // Act + string s = "?"; + int i = -1; + var expected = users.Select(u => u.TryParseWithArgumentAndTwoOut(u.UserName, out s, out i)); + var result = users.AsQueryable().Select(config, "TryParseWithArgumentAndTwoOut(UserName, out _, out _)"); + + // Assert + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public void ExpressionTests_MethodCall_WithoutArgument_And_OutArgument() + { + // Arrange + var config = CreateParsingConfigForMethodCallTests(); + var users = User.GenerateSampleModels(5); + + // Act + string s = ""; + var expected = users.Select(u => u.TryParseWithoutArgument(out s)); + var result = users.AsQueryable().Select(config, "TryParseWithoutArgument(out _)"); + + // Assert + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public void ExpressionTests_MethodCall_WithArgument_And_NoDiscardForOutArgument_ThrowsException() + { + // Arrange + var config = CreateParsingConfigForMethodCallTests(); + var users = User.GenerateSampleModels(5); + + // Act + Action action = () => users.AsQueryable().Select(config, "TryParseWithArgument(it.UserName, $out x)"); + + // Assert + action.Should().Throw().WithMessage("When using an out variable, a discard '_' is required."); + } + [Fact] public void ExpressionTests_MethodCall_NoParams() { diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs index eec7384cb..801f8099b 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs @@ -1,70 +1,92 @@ using System.Collections.Generic; -namespace System.Linq.Dynamic.Core.Tests.Helpers.Models +namespace System.Linq.Dynamic.Core.Tests.Helpers.Models; + +public class User { - public class User - { - public Guid Id { get; set; } + public Guid Id { get; set; } - public SnowflakeId SnowflakeId { get; set; } + public SnowflakeId SnowflakeId { get; set; } - public string UserName { get; set; } + public string UserName { get; set; } - public int? NullableInt { get; set; } + public int? NullableInt { get; set; } - public int Income { get; set; } + public int Income { get; set; } - public UserProfile Profile { get; set; } + public UserProfile Profile { get; set; } - public UserState State { get; set; } + public UserState State { get; set; } - public List Roles { get; set; } + public List Roles { get; set; } - public bool TestMethod1() - { - return true; - } + public bool TestMethod1() + { + return true; + } + + public bool TestMethod2(User other) + { + return true; + } + + public bool TestMethod3(User other) + { + return Id == other.Id; + } + + public bool TryParseWithoutArgument(out string xxx) + { + return TryParseWithArgument(UserName, out xxx); + } - public bool TestMethod2(User other) + public bool TryParseWithArgument(string s, out string xxx) + { + if (s.EndsWith("1") || s.EndsWith("2")) { + xxx = UserName; return true; } - public bool TestMethod3(User other) - { - return Id == other.Id; - } + xxx = ""; + return false; + } + + public bool TryParseWithArgumentAndTwoOut(string s, out string xxx, out int x) + { + x = 0; + return TryParseWithArgument(s, out xxx) && int.TryParse(s, out x); + } + + public static IList GenerateSampleModels(int total, bool allowNullableProfiles = false) + { + var list = new List(); - public static IList GenerateSampleModels(int total, bool allowNullableProfiles = false) + for (int i = 0; i < total; i++) { - var list = new List(); + var user = new User + { + Id = Guid.NewGuid(), + SnowflakeId = new SnowflakeId(((ulong)long.MaxValue + (ulong)i + 2UL)), + UserName = "User" + i, + Income = 1 + (i % 15) * 100 + }; - for (int i = 0; i < total; i++) + if (!allowNullableProfiles || (i % 8) != 5) { - var user = new User + user.Profile = new UserProfile { - Id = Guid.NewGuid(), - SnowflakeId = new SnowflakeId(((ulong)long.MaxValue + (ulong)i + 2UL)), - UserName = "User" + i, - Income = 1 + (i % 15) * 100 + FirstName = "FirstName" + i, + LastName = "LastName" + i, + Age = (i % 50) + 18 }; - - if (!allowNullableProfiles || (i % 8) != 5) - { - user.Profile = new UserProfile - { - FirstName = "FirstName" + i, - LastName = "LastName" + i, - Age = (i % 50) + 18 - }; - } - - user.Roles = new List(Role.StandardRoles); - - list.Add(user); } - return list.ToArray(); + user.Roles = new List(Role.StandardRoles); + + list.Add(user); } + + return list.ToArray(); } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs index 16564d945..a1afadd49 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/MethodFinderTest.cs @@ -3,21 +3,20 @@ using Xunit; using static System.Linq.Expressions.Expression; -namespace System.Linq.Dynamic.Core.Tests.Parser +namespace System.Linq.Dynamic.Core.Tests.Parser; + +public class MethodFinderTest { - public class MethodFinderTest + [Fact] + public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual() { - [Fact] - public void MethodsOfDynamicLinqAndSystemLinqShouldBeEqual() - { - Expression> expr = x => x.ToString(); + Expression> expr = x => x.ToString(); - var selector = "ToString()"; - var prm = Parameter(typeof(int?)); - var parser = new ExpressionParser(new[] { prm }, selector, new object[] { }, ParsingConfig.Default); - var expr1 = parser.Parse(null); + var selector = "ToString()"; + var prm = Parameter(typeof(int?)); + var parser = new ExpressionParser(new[] { prm }, selector, new object[] { }, ParsingConfig.Default); + var expr1 = parser.Parse(null); - Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType); - } + Assert.Equal(((MethodCallExpression)expr.Body).Method.DeclaringType, ((MethodCallExpression)expr1).Method.DeclaringType); } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj index 1f82ea88a..3a27dfd4a 100644 --- a/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj +++ b/test/System.Linq.Dynamic.Core.Tests/System.Linq.Dynamic.Core.Tests.csproj @@ -6,6 +6,7 @@ full True latest + enable ../../src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.snk {912FBF24-3CAE-4A50-B5EA-E525B9FAEC80} From 0b56ddda4dbfb669ebea3e07f48cd0270784bfcf Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 10 Sep 2023 11:57:26 +0200 Subject: [PATCH 005/153] Add config setting: NewOperatorIsNotAllowed (#732) --- .../Parser/ExpressionParser.cs | 7 ++++- src/System.Linq.Dynamic.Core/ParsingConfig.cs | 13 ++++++-- src/System.Linq.Dynamic.Core/Res.cs | 1 + .../ExpressionParserTests.MemberAccess.cs | 8 ++--- .../ExpressionParserTests.TypeAccess.cs | 31 +++++++++++++++++-- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index ada4b2b95..32a7bc548 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -998,6 +998,10 @@ private Expression ParseIdentifier() return ParseFunctionIsNull(); case KeywordsHelper.FUNCTION_NEW: + if (_parsingConfig.DisallowNewKeyword) + { + throw ParseError(Res.NewOperatorIsNotAllowed); + } return ParseNew(); case KeywordsHelper.FUNCTION_NULLPROPAGATION: @@ -1354,9 +1358,10 @@ private Expression ParseNew() if (_textParser.CurrentToken.Id == TokenId.Identifier) { var newTypeName = _textParser.CurrentToken.Text; + _textParser.NextToken(); - while (_textParser.CurrentToken.Id == TokenId.Dot || _textParser.CurrentToken.Id == TokenId.Plus) + while (_textParser.CurrentToken.Id is TokenId.Dot or TokenId.Plus) { var sep = _textParser.CurrentToken.Text; _textParser.NextToken(); diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 6b5740549..157aadd02 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -14,12 +14,12 @@ public class ParsingConfig /// /// Default ParsingConfig /// - public static ParsingConfig Default { get; } = new ParsingConfig(); + public static ParsingConfig Default { get; } = new(); /// /// Default ParsingConfig for EntityFramework Core 2.1 and higher /// - public static ParsingConfig DefaultEFCore21 { get; } = new ParsingConfig + public static ParsingConfig DefaultEFCore21 { get; } = new() { EvaluateGroupByAtDatabase = true }; @@ -30,7 +30,7 @@ public class ParsingConfig /// /// Default ParsingConfig for CosmosDb /// - public static ParsingConfig DefaultCosmosDb { get; } = new ParsingConfig + public static ParsingConfig DefaultCosmosDb { get; } = new() { RenameEmptyParameterExpressionNames = true }; @@ -227,4 +227,11 @@ public IQueryableAnalyzer QueryableAnalyzer /// Default value is false. /// public bool SupportDotInPropertyNames { get; set; } = false; + + /// + /// Disallows the New() keyword to be used to construct a class. + /// + /// Default value is false. + /// + public bool DisallowNewKeyword { get; set; } = false; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Res.cs b/src/System.Linq.Dynamic.Core/Res.cs index 0b897a856..faaaa21a1 100644 --- a/src/System.Linq.Dynamic.Core/Res.cs +++ b/src/System.Linq.Dynamic.Core/Res.cs @@ -54,6 +54,7 @@ internal static class Res public const string MinusCannotBeAppliedToUnsignedInteger = "'-' cannot be applied to unsigned integers."; public const string MissingAsClause = "Expression is missing an 'as' clause"; public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other"; + public const string NewOperatorIsNotAllowed = "Using the new operator is not allowed via the ParsingConfig."; public const string NoApplicableAggregate = "No applicable aggregate method '{0}({1})' exists"; public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'"; public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'"; diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.MemberAccess.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.MemberAccess.cs index a21c9814c..8f266e693 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.MemberAccess.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.MemberAccess.cs @@ -1,6 +1,4 @@ -using System.Linq.Dynamic.Core.Parser; -using System.Linq.Expressions; -using FluentAssertions; +using FluentAssertions; using Xunit; namespace System.Linq.Dynamic.Core.Tests.Parser @@ -11,7 +9,7 @@ partial class ExpressionParserTests public void ParseMemberAccess_DictionaryIndex_On_Dynamic() { // Arrange - var products = (new ProductDynamic[0]).AsQueryable(); + var products = new ProductDynamic[0].AsQueryable(); // Act var expression = products.Where("Properties.Name == @0", "First Product").Expression; @@ -31,4 +29,4 @@ public class ProductDynamic public dynamic Properties { get; set; } } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs index 9c92c1b39..45548e497 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/ExpressionParserTests.TypeAccess.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq.Dynamic.Core.Exceptions; using System.Linq.Dynamic.Core.Parser; using System.Linq.Expressions; using FluentAssertions; @@ -216,9 +217,10 @@ public void ParseTypeAccess_Via_Constructor_DynamicType_To_String(string newExpr // Arrange var parameter = Expression.Parameter(typeof(int)); var parameter2 = Expression.Parameter(typeof(int)); - var returnType = DynamicClassFactory.CreateType(new List { - new DynamicProperty("a", typeof(int)), - new DynamicProperty("b", typeof(int)) + var returnType = DynamicClassFactory.CreateType(new List + { + new("a", typeof(int)), + new("b", typeof(int)) }); // Act @@ -228,4 +230,27 @@ public void ParseTypeAccess_Via_Constructor_DynamicType_To_String(string newExpr // Assert expression.ToString().Should().Match(newExpression2); } + + [Fact] + public void Parse_NewOperator_When_DisallowNewKeyword_Is_True_Should_Throw_Exception() + { + // Arrange + var parameter = Expression.Parameter(typeof(int)); + var returnType = DynamicClassFactory.CreateType(new List + { + new("a", typeof(int)) + }); + var config = new ParsingConfig + { + DisallowNewKeyword = true + }; + + // Act + var parser = new ExpressionParser(new[] { parameter }, "new(1 as a)", new object[] { }, config); + + Action action = () => parser.Parse(returnType); + + // Assert + action.Should().Throw().Which.Message.Should().Be(Res.NewOperatorIsNotAllowed); + } } \ No newline at end of file From 6c38a1343ef18be4ebdf09b32a9eb0f8e1d536a4 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 10 Sep 2023 12:21:13 +0200 Subject: [PATCH 006/153] Add some more tests for Max (#744) --- .../Helpers/Models/User.cs | 10 +- .../QueryableTests.Max.cs | 114 +++++++++++++----- 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs index 801f8099b..b758d408e 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs @@ -10,6 +10,10 @@ public class User public string UserName { get; set; } + public DateTime BirthDate { get; set; } + + public DateTime? EndDate { get; set; } + public int? NullableInt { get; set; } public int Income { get; set; } @@ -69,10 +73,12 @@ public static IList GenerateSampleModels(int total, bool allowNullableProf Id = Guid.NewGuid(), SnowflakeId = new SnowflakeId(((ulong)long.MaxValue + (ulong)i + 2UL)), UserName = "User" + i, - Income = 1 + (i % 15) * 100 + Income = 1 + (i % 15) * 100, + BirthDate = DateTime.UtcNow.AddYears(-50), + EndDate = i % 2 == 0 ? DateTime.UtcNow.AddYears(99) : null }; - if (!allowNullableProfiles || (i % 8) != 5) + if (!allowNullableProfiles || i % 8 != 5) { user.Profile = new UserProfile { diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Max.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Max.cs index f539a0d1d..517d59589 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Max.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Max.cs @@ -1,36 +1,90 @@ using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using FluentAssertions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tests; + +public partial class QueryableTests { - public partial class QueryableTests + [Fact] + public void Max() + { + // Arrange + var incomes = User.GenerateSampleModels(10).Select(u => u.Income).ToArray(); + + // Act + var expected = incomes.Max(); + var actual = incomes.AsQueryable().Max(); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void Max_Selector() { - [Fact] - public void Max() - { - // Arrange - var incomes = User.GenerateSampleModels(100).Select(u => u.Income); - - // Act - var expected = incomes.Max(); - var actual = incomes.AsQueryable().Max(); - - // Assert - Assert.Equal(expected, actual); - } - - [Fact] - public void Max_Selector() - { - // Arrange - var users = User.GenerateSampleModels(100); - - // Act - var expected = users.Max(u => u.Income); - var result = users.AsQueryable().Max("Income"); - - // Assert - Assert.Equal(expected, result); - } + // Arrange + var users = User.GenerateSampleModels(10); + + // Act + var expected = users.Max(u => u.Income); + var result = users.AsQueryable().Max("Income"); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void Max_Where_On_Int() + { + // Arrange + var users = User.GenerateSampleModels(10); + + // Act + var typed = users + .Where(u => users.Max(m => m.Income) == u.Income) + .ToList(); + var dynamic = users.AsQueryable() + .Where("@0.Max(Income) == Income", users) + .ToList(); + + // Assert + dynamic.Should().BeEquivalentTo(typed); + } + + [Fact] + public void Max_Where_On_DateTime() + { + // Arrange + var users = User.GenerateSampleModels(10); + + // Act + var typed = users + .Where(u => users.Max(m => m.BirthDate) == u.BirthDate) + .ToList(); + var dynamic = users.AsQueryable() + .Where("@0.Max(BirthDate) == BirthDate", users) + .ToList(); + + // Assert + dynamic.Should().BeEquivalentTo(typed); + } + + [Fact] + public void Max_Where_On_NullableDateTime() + { + // Arrange + var users = User.GenerateSampleModels(10); + + // Act + var typed = users + .Where(u => users.Max(m => m.EndDate) == u.EndDate) + .ToList(); + var dynamic = users.AsQueryable() + .Where("@0.Max(EndDate) == EndDate", users) + .ToList(); + + // Assert + dynamic.Should().BeEquivalentTo(typed); } -} +} \ No newline at end of file From c4b99beab9de683ec4252943b04c85e5a779362e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 21 Sep 2023 13:15:40 +0200 Subject: [PATCH 007/153] v1.3.5 --- CHANGELOG.md | 13 +++++++++++++ Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index afa0b23e5..14c8fc02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# v1.3.5 (21 September 2023) +- [#728](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/728) - added symbol check when checking for identifier [feature] contributed by [abbasc52](https://github.com/abbasc52) +- [#730](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/730) - Add PackageReadme to NuGet [feature] contributed by [StefH](https://github.com/StefH) +- [#731](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/731) - Add logic to convert any array to object array. [feature] contributed by [StefH](https://github.com/StefH) +- [#732](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/732) - Add config setting: DisallowNewKeyword [feature] contributed by [StefH](https://github.com/StefH) +- [#739](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/739) - Fix AddSignatures and SubtractSignatures [bug] contributed by [StefH](https://github.com/StefH) +- [#743](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/743) - Add support for out keyword [feature] contributed by [StefH](https://github.com/StefH) +- [#744](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/744) - Add some more tests for Max [test] contributed by [StefH](https://github.com/StefH) +- [#389](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/389) - ExpressionPromoter.Promote Limitations to Detect plausible method matches [feature] +- [#727](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/727) - Type from registered type has higher preference than input parameter expression with same name [feature] +- [#737](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/737) - Exception when trying to add/subtract types 'DateTime' and 'TimeSpan' [bug] +- [#741](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/741) - out keyword is not working with dynamic linq [feature] + # v1.3.4 (03 August 2023) - [#723](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/723) - Handle UseParameterizedNamesInDynamicQuery when parsing dynamic OfType function contributed by [yonguelink](https://github.com/yonguelink) - [#722](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/722) - UseParameterizedNamesInDynamicQuery set to true breaks `OfType` method call diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index 140b51907..b484b864d 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.4 +SET version=v1.3.5 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index e265d039e..1e9ea2a1d 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 4 + 5 \ No newline at end of file From 70a329818fc9b49c1fff4b8dc4be5843f8db5cac Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 14 Oct 2023 10:55:20 +0200 Subject: [PATCH 008/153] DynamicLinqTypeAttribute can now also be defined on an interface (#750) --- .../DynamicLinqTypeAttribute.cs | 17 +++++----- .../DynamicExpressionParserTests.cs | 31 +++++++++++++++++-- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DynamicLinqTypeAttribute.cs b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DynamicLinqTypeAttribute.cs index 5db6901c9..13fc23d2e 100644 --- a/src/System.Linq.Dynamic.Core/CustomTypeProviders/DynamicLinqTypeAttribute.cs +++ b/src/System.Linq.Dynamic.Core/CustomTypeProviders/DynamicLinqTypeAttribute.cs @@ -1,10 +1,9 @@ -namespace System.Linq.Dynamic.Core.CustomTypeProviders +namespace System.Linq.Dynamic.Core.CustomTypeProviders; + +/// +/// Indicates to Dynamic Linq to consider the Type as a valid dynamic linq type. +/// +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = false, Inherited = false)] +public sealed class DynamicLinqTypeAttribute : Attribute { - /// - /// Indicates to Dynamic Linq to consider the Type as a valid dynamic linq type. - /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = false, Inherited = false)] - public sealed class DynamicLinqTypeAttribute : Attribute - { - } -} +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index acf8748e2..a6a817726 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -88,6 +88,17 @@ public class CustomClassWithMethodWithDynamicLinqTypeAttribute public int GetAge(int x) => x; } + [DynamicLinqType] + public interface ICustomInterfaceWithMethodWithDynamicLinqTypeAttribute + { + int GetAge(int x); + } + + public class CustomClassImplementingInterface : ICustomInterfaceWithMethodWithDynamicLinqTypeAttribute + { + public int GetAge(int x) => x; + } + [DynamicLinqType] public class CustomClassWithStaticMethodWithDynamicLinqTypeAttribute { @@ -1057,7 +1068,7 @@ public void DynamicExpressionParser_ParseLambda_CustomStaticMethod_WhenClassHasD // Act var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(CustomClassWithStaticMethodWithDynamicLinqTypeAttribute), null, expression); var del = lambdaExpression.Compile(); - var result = (int)del.DynamicInvoke(context); + var result = (int?)del.DynamicInvoke(context); // Assert Check.That(result).IsEqualTo(10); @@ -1073,12 +1084,28 @@ public void DynamicExpressionParser_ParseLambda_CustomMethod_WhenClassHasDynamic // Act var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(CustomClassWithMethodWithDynamicLinqTypeAttribute), null, expression); var del = lambdaExpression.Compile(); - var result = (int)del.DynamicInvoke(context); + var result = (int?)del.DynamicInvoke(context); // Assert Check.That(result).IsEqualTo(10); } + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomInterface_WhenInterfaceHasDynamicLinqTypeAttribute_ShouldWorkCorrect() + { + // Arrange + var context = new CustomClassImplementingInterface(); + var expression = $"{nameof(ICustomInterfaceWithMethodWithDynamicLinqTypeAttribute.GetAge)}(10)"; + + // Act + var lambdaExpression = DynamicExpressionParser.ParseLambda(typeof(ICustomInterfaceWithMethodWithDynamicLinqTypeAttribute), null, expression); + var del = lambdaExpression.Compile(); + var result = (int?)del.DynamicInvoke(context); + + // Assert + result.Should().Be(10); + } + [Fact] public void DynamicExpressionParser_ParseLambda_CustomMethod_WhenClassDoesNotHaveDynamicLinqTypeAttribute_ShouldThrowException() { From c41f87d1ffa132135f85b6ac8fd10422c8778c14 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 29 Oct 2023 09:35:01 +0100 Subject: [PATCH 009/153] Fix KeywordsHelper (#755) --- .../Parser/ExpressionParser.cs | 1 - .../Parser/IKeywordsHelper.cs | 13 +- .../Parser/KeywordsHelper.cs | 175 +++++++++++------- .../Parser/PredefinedTypesHelper.cs | 17 +- .../DynamicExpressionParserTests.cs | 12 ++ 5 files changed, 137 insertions(+), 81 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 32a7bc548..709622d0a 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -957,7 +957,6 @@ private Expression ParseIdentifier() var isValidKeyWord = _keywordsHelper.TryGetValue(_textParser.CurrentToken.Text, out var value); - bool shouldPrioritizeType = true; if (_parsingConfig.PrioritizePropertyOrFieldOverTheType && value is Type) diff --git a/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs index 529713213..ee60f36c8 100644 --- a/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/IKeywordsHelper.cs @@ -1,7 +1,8 @@ -namespace System.Linq.Dynamic.Core.Parser +using System.Diagnostics.CodeAnalysis; + +namespace System.Linq.Dynamic.Core.Parser; + +interface IKeywordsHelper { - interface IKeywordsHelper - { - bool TryGetValue(string name, out object type); - } -} + bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs index 087b45e5c..b6e852d1c 100644 --- a/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/KeywordsHelper.cs @@ -1,89 +1,126 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal class KeywordsHelper : IKeywordsHelper { - internal class KeywordsHelper : IKeywordsHelper + public const string KEYWORD_IT = "it"; + public const string KEYWORD_PARENT = "parent"; + public const string KEYWORD_ROOT = "root"; + + public const string SYMBOL_IT = "$"; + public const string SYMBOL_PARENT = "^"; + public const string SYMBOL_ROOT = "~"; + + public const string FUNCTION_IIF = "iif"; + public const string FUNCTION_ISNULL = "isnull"; + public const string FUNCTION_NEW = "new"; + public const string FUNCTION_NULLPROPAGATION = "np"; + public const string FUNCTION_IS = "is"; + public const string FUNCTION_AS = "as"; + public const string FUNCTION_CAST = "cast"; + + private readonly ParsingConfig _config; + + // Keywords are IgnoreCase + private readonly Dictionary _keywordMapping = new(StringComparer.OrdinalIgnoreCase) { - public const string SYMBOL_IT = "$"; - public const string SYMBOL_PARENT = "^"; - public const string SYMBOL_ROOT = "~"; - - public const string KEYWORD_IT = "it"; - public const string KEYWORD_PARENT = "parent"; - public const string KEYWORD_ROOT = "root"; - - public const string FUNCTION_IIF = "iif"; - public const string FUNCTION_ISNULL = "isnull"; - public const string FUNCTION_NEW = "new"; - public const string FUNCTION_NULLPROPAGATION = "np"; - public const string FUNCTION_IS = "is"; - public const string FUNCTION_AS = "as"; - public const string FUNCTION_CAST = "cast"; - - private readonly IDictionary _keywords = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "true", Expression.Constant(true) }, - { "false", Expression.Constant(false) }, - { "null", Expression.Constant(null) } - }; + { "true", Expression.Constant(true) }, + { "false", Expression.Constant(false) }, + { "null", Expression.Constant(null) }, + + { SYMBOL_IT, SYMBOL_IT }, + { SYMBOL_PARENT, SYMBOL_PARENT }, + { SYMBOL_ROOT, SYMBOL_ROOT }, + + { FUNCTION_IIF, FUNCTION_IIF }, + { FUNCTION_ISNULL, FUNCTION_ISNULL }, + { FUNCTION_NEW, FUNCTION_NEW }, + { FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION }, + { FUNCTION_IS, FUNCTION_IS }, + { FUNCTION_AS, FUNCTION_AS }, + { FUNCTION_CAST, FUNCTION_CAST } + }; + + // PreDefined Types are not IgnoreCase + private static readonly Dictionary _preDefinedTypeMapping = new(); + + // Custom DefinedTypes are not IgnoreCase + private readonly Dictionary _customTypeMapping = new(); - public KeywordsHelper(ParsingConfig config) + static KeywordsHelper() + { + foreach (var type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) { - if (config.AreContextKeywordsEnabled) - { - _keywords.Add(KEYWORD_IT, KEYWORD_IT); - _keywords.Add(KEYWORD_PARENT, KEYWORD_PARENT); - _keywords.Add(KEYWORD_ROOT, KEYWORD_ROOT); - } + _preDefinedTypeMapping[type.FullName!] = type; + _preDefinedTypeMapping[type.Name] = type; + } + } - _keywords.Add(SYMBOL_IT, SYMBOL_IT); - _keywords.Add(SYMBOL_PARENT, SYMBOL_PARENT); - _keywords.Add(SYMBOL_ROOT, SYMBOL_ROOT); + public KeywordsHelper(ParsingConfig config) + { + _config = Check.NotNull(config); - _keywords.Add(FUNCTION_IIF, FUNCTION_IIF); - _keywords.Add(FUNCTION_ISNULL, FUNCTION_ISNULL); - _keywords.Add(FUNCTION_NEW, FUNCTION_NEW); - _keywords.Add(FUNCTION_NULLPROPAGATION, FUNCTION_NULLPROPAGATION); - _keywords.Add(FUNCTION_IS, FUNCTION_IS); - _keywords.Add(FUNCTION_AS, FUNCTION_AS); - _keywords.Add(FUNCTION_CAST, FUNCTION_CAST); + if (config.AreContextKeywordsEnabled) + { + _keywordMapping.Add(KEYWORD_IT, KEYWORD_IT); + _keywordMapping.Add(KEYWORD_PARENT, KEYWORD_PARENT); + _keywordMapping.Add(KEYWORD_ROOT, KEYWORD_ROOT); + } - foreach (Type type in PredefinedTypesHelper.PredefinedTypes.OrderBy(kvp => kvp.Value).Select(kvp => kvp.Key)) + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (config.CustomTypeProvider != null) + { + foreach (var type in config.CustomTypeProvider.GetCustomTypes()) { - if (!string.IsNullOrEmpty(type.FullName)) - { - _keywords[type.FullName] = type; - } - _keywords[type.Name] = type; + _customTypeMapping[type.FullName!] = type; + _customTypeMapping[type.Name] = type; } + } + } - foreach (var pair in PredefinedTypesHelper.PredefinedTypesShorthands) - { - _keywords.Add(pair.Key, pair.Value); - } + public bool TryGetValue(string name, [NotNullWhen(true)] out object? keyWordOrType) + { + // 1. Try to get as keyword + if (_keywordMapping.TryGetValue(name, out var keyWord)) + { + keyWordOrType = keyWord; + return true; + } - if (config.SupportEnumerationsFromSystemNamespace) - { - foreach (var pair in EnumerationsFromMscorlib.PredefinedEnumerationTypes) - { - _keywords.Add(pair.Key, pair.Value); - } - } + // 2. Try to get as predefined shorttype ("bool", "char", ...) + if (PredefinedTypesHelper.PredefinedTypesShorthands.TryGetValue(name, out var predefinedShortHandType)) + { + keyWordOrType = predefinedShortHandType; + return true; + } - if (config.CustomTypeProvider != null) - { - foreach (Type type in config.CustomTypeProvider.GetCustomTypes()) - { - _keywords[type.FullName] = type; - _keywords[type.Name] = type; - } - } + // 3. Try to get as predefined type ("Boolean", "System.Boolean", ..., "DateTime", "System.DateTime", ...) + if (_preDefinedTypeMapping.TryGetValue(name, out var predefinedType)) + { + keyWordOrType = predefinedType; + return true; } - public bool TryGetValue(string name, out object type) + // 4. Try to get as an enum from the system namespace + if (_config.SupportEnumerationsFromSystemNamespace && EnumerationsFromMscorlib.PredefinedEnumerationTypes.TryGetValue(name, out var predefinedEnumType)) { - return _keywords.TryGetValue(name, out type); + keyWordOrType = predefinedEnumType; + return true; } + + // 5. Try to get as custom type + if (_customTypeMapping.TryGetValue(name, out var customType)) + { + keyWordOrType = customType; + return true; + } + + // 6. Not found, return false + keyWordOrType = null; + return false; } -} +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs index 21ea7fafc..2e37d57d1 100644 --- a/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/PredefinedTypesHelper.cs @@ -13,14 +13,21 @@ internal static class PredefinedTypesHelper // These shorthands have different name than actual type and therefore not recognized by default from the PredefinedTypes. public static readonly IDictionary PredefinedTypesShorthands = new Dictionary { + { "bool", typeof(bool) }, + { "byte", typeof(byte) }, + { "char", typeof(char) }, + { "decimal", typeof(decimal) }, + { "double", typeof(double) }, + { "float", typeof(float) }, { "int", typeof(int) }, - { "uint", typeof(uint) }, - { "short", typeof(short) }, - { "ushort", typeof(ushort) }, { "long", typeof(long) }, + { "object", typeof(object) }, + { "sbyte", typeof(sbyte) }, + { "short", typeof(short) }, + { "string", typeof(string) }, + { "uint", typeof(uint) }, { "ulong", typeof(ulong) }, - { "bool", typeof(bool) }, - { "float", typeof(float) } + { "ushort", typeof(ushort) }, }; public static readonly IDictionary PredefinedTypes = new ConcurrentDictionary(new Dictionary diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index a6a817726..9dea54f57 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -16,6 +16,11 @@ namespace System.Linq.Dynamic.Core.Tests; public class DynamicExpressionParserTests { + [DynamicLinqType] + public class Z + { + } + public class Foo { public Foo FooValue { get; set; } @@ -1786,6 +1791,13 @@ public void DynamicExpressionParser_ParseLambda_HandleIntArray_For_StringJoin() result.Should().Be("1,2,3"); } + [Fact] + public void DynamicExpressionParser_ParseLambda_LambdaParameter_SameNameAsDynamicType() + { + // Act + DynamicExpressionParser.ParseLambda(new ParsingConfig(), false, "new[]{1,2,3}.Any(z => z > 0)"); + } + public class DefaultDynamicLinqCustomTypeProviderForGenericExtensionMethod : DefaultDynamicLinqCustomTypeProvider { public override HashSet GetCustomTypes() => new HashSet(base.GetCustomTypes()) { typeof(Methods), typeof(MethodsItemExtension) }; From c09452a67212923dda73a7910027f7ada3c0fc19 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 29 Oct 2023 10:54:37 +0100 Subject: [PATCH 010/153] v1.3.6 --- CHANGELOG.md | 6 ++++++ Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14c8fc02e..13568d1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v1.3.6 (29 October 2023) +- [#750](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/750) - DynamicLinqTypeAttribute can now also be defined on an interface [feature] contributed by [StefH](https://github.com/StefH) +- [#755](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/755) - Fix KeywordsHelper [bug] contributed by [StefH](https://github.com/StefH) +- [#745](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/745) - The `DynamicLinqType` attribute can not be added to interfaces. [refactor] +- [#754](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/754) - Parameters of lambdas in the supplied expression have lower priority than defined types, even when casing differs [bug] + # v1.3.5 (21 September 2023) - [#728](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/728) - added symbol check when checking for identifier [feature] contributed by [abbasc52](https://github.com/abbasc52) - [#730](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/730) - Add PackageReadme to NuGet [feature] contributed by [StefH](https://github.com/StefH) diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index b484b864d..cff730338 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.5 +SET version=v1.3.6 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index 1e9ea2a1d..80460ce54 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 5 + 6 \ No newline at end of file From ea4efac5b278c3c9ca0398ba7eea56386e6eb37a Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 19 Nov 2023 13:22:21 +0100 Subject: [PATCH 011/153] v1.3.6 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13568d1f5..822bd6e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v1.3.6 (29 October 2023) +# v1.3.6 (19 November 2023) - [#750](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/750) - DynamicLinqTypeAttribute can now also be defined on an interface [feature] contributed by [StefH](https://github.com/StefH) - [#755](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/755) - Fix KeywordsHelper [bug] contributed by [StefH](https://github.com/StefH) - [#745](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/745) - The `DynamicLinqType` attribute can not be added to interfaces. [refactor] From 3949e67c47f71fe8326914eb597ada016f449cb0 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 20 Nov 2023 16:15:13 +0100 Subject: [PATCH 012/153] Add support for .NET 8 + Microsoft.EntityFrameworkCore 8 (#756) * Add .NET 8 * fix ci * readme --- .github/workflows/ci.yml | 18 +++--- README.md | 2 +- System.Linq.Dynamic.Core.sln | 38 +++++++++++++ ...tyFrameworkCore.DynamicLinq.EFCore8.csproj | 48 ++++++++++++++++ .../Properties/AssemblyInfo.cs | 6 ++ .../System.Linq.Dynamic.Core.csproj | 6 +- ...System.Linq.Dynamic.Core.Tests.Net6.csproj | 1 - ...System.Linq.Dynamic.Core.Tests.Net8.csproj | 57 +++++++++++++++++++ .../EntitiesTests.FormattableString.cs | 1 + .../EntitiesTests.TakeWhile.cs | 1 + .../Parser/StringParserTests.cs | 2 +- .../QueryableTests.Where.cs | 2 +- 12 files changed, 168 insertions(+), 14 deletions(-) create mode 100644 src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8.csproj create mode 100644 src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 47d6cc388..c00b9b2db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,18 +12,22 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' - - - uses: actions/setup-dotnet@v1 - with: - dotnet-version: '7.0.x' - + dotnet-version: | + 6.0.x + 7.0.x + 8.0.x + - name: Build Projects run: | dotnet build ./src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj -c Release -p:buildType=azure-pipelines-ci + - name: Run Tests net8.0 + run: | + dotnet build ./test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj -c Release -p:buildType=azure-pipelines-ci + dotnet test ./test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj -c Release -p:buildType=azure-pipelines-ci --no-build + - name: Run Tests net7.0 run: | dotnet build ./test/System.Linq.Dynamic.Core.Tests.Net7/System.Linq.Dynamic.Core.Tests.Net7.csproj -c Release -p:buildType=azure-pipelines-ci diff --git a/README.md b/README.md index abe2b88d9..6f04f55de 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ If it's not possible to add that attribute, you need to implement a custom [Cust The following frameworks are supported: - net35, net40, net45, net46 and up - netstandard1.3, netstandard2.0 and netstandard2.1 -- netcoreapp3.1, net5.0, net6.0 and net7.0 +- netcoreapp3.1, net5.0, net6.0, net7.0 and net8.0 - uap10.0 ### Fork details diff --git a/System.Linq.Dynamic.Core.sln b/System.Linq.Dynamic.Core.sln index 4c404474d..0c684bf37 100644 --- a/System.Linq.Dynamic.Core.sln +++ b/System.Linq.Dynamic.Core.sln @@ -132,6 +132,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.EntityFrameworkCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Dynamic.Core.Tests.Net7", "test\System.Linq.Dynamic.Core.Tests.Net7\System.Linq.Dynamic.Core.Tests.Net7.csproj", "{CC63ECEB-18C1-462B-BAFC-7F146A7C2740}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8", "src\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8\Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8.csproj", "{9000129D-322D-4FE6-9C47-75464577C374}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Dynamic.Core.Tests.Net8", "test\System.Linq.Dynamic.Core.Tests.Net8\System.Linq.Dynamic.Core.Tests.Net8.csproj", "{ABB1BF71-8927-49BB-99F3-70BCB2CD161E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -802,6 +806,38 @@ Global {CC63ECEB-18C1-462B-BAFC-7F146A7C2740}.Release|x64.Build.0 = Release|Any CPU {CC63ECEB-18C1-462B-BAFC-7F146A7C2740}.Release|x86.ActiveCfg = Release|Any CPU {CC63ECEB-18C1-462B-BAFC-7F146A7C2740}.Release|x86.Build.0 = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|ARM.ActiveCfg = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|ARM.Build.0 = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|x64.ActiveCfg = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|x64.Build.0 = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|x86.ActiveCfg = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Debug|x86.Build.0 = Debug|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|Any CPU.Build.0 = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|ARM.ActiveCfg = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|ARM.Build.0 = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|x64.ActiveCfg = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|x64.Build.0 = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|x86.ActiveCfg = Release|Any CPU + {9000129D-322D-4FE6-9C47-75464577C374}.Release|x86.Build.0 = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|ARM.ActiveCfg = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|ARM.Build.0 = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|x64.ActiveCfg = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|x64.Build.0 = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Debug|x86.Build.0 = Debug|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|Any CPU.Build.0 = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|ARM.ActiveCfg = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|ARM.Build.0 = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x64.ActiveCfg = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x64.Build.0 = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x86.ActiveCfg = Release|Any CPU + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -848,6 +884,8 @@ Global {B133DA55-339D-4600-AED3-46214AD9F08A} = {122BC4FA-7563-4E35-9D17-077F16F1629F} {FB2F4C99-EC34-4D29-87E2-944B25D90EF7} = {DBD7D9B6-FCC7-4650-91AF-E6457573A68F} {CC63ECEB-18C1-462B-BAFC-7F146A7C2740} = {8463ED7E-69FB-49AE-85CF-0791AFD98E38} + {9000129D-322D-4FE6-9C47-75464577C374} = {DBD7D9B6-FCC7-4650-91AF-E6457573A68F} + {ABB1BF71-8927-49BB-99F3-70BCB2CD161E} = {8463ED7E-69FB-49AE-85CF-0791AFD98E38} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {94C56722-194E-4B8B-BC23-B3F754E89A20} diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8.csproj b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8.csproj new file mode 100644 index 000000000..a380c22c6 --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8.csproj @@ -0,0 +1,48 @@ + + + + + Microsoft.EntityFrameworkCore.DynamicLinq + ../Microsoft.EntityFrameworkCore.DynamicLinq.EFCore2/Microsoft.EntityFrameworkCore.DynamicLinq.snk + Microsoft.EntityFrameworkCore.DynamicLinq + $(DefineConstants);EFCORE;EFCORE_3X;EFDYNAMICFUNCTIONS;ASYNCENUMERABLE + Dynamic Linq extensions for Microsoft.EntityFrameworkCore which adds Async support + system;linq;dynamic;entityframework;core;async + {9000129D-322D-4FE6-9C47-75464577C374} + net8.0 + 8.3.$(PatchVersion) + + + + full + + + + + portable + true + + + + net8.0 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..54dd50afb --- /dev/null +++ b/src/Microsoft.EntityFrameworkCore.DynamicLinq.EFCore8/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.InteropServices; + +[assembly: ComVisible(false)] +#if !(WINDOWS_APP || NETSTANDARD2_1) +[assembly: Guid("b467c675-c014-4b55-85b9-9578941d2ef7")] +#endif diff --git a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj index a1cfec9fe..b77979266 100644 --- a/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj +++ b/src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.csproj @@ -9,7 +9,7 @@ This is a .NETStandard / .NET Core port of the the Microsoft assembly for the .Net 4.0 Dynamic language functionality. system;linq;dynamic;core;dotnet;NETCoreApp;NETStandard {D3804228-91F4-4502-9595-39584E510002} - net35;net40;net45;net452;net46;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0 + net35;net40;net45;net452;net46;netstandard1.3;netstandard2.0;netstandard2.1;uap10.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 1.3.$(PatchVersion) @@ -26,14 +26,14 @@ - net40;net45;net46;netstandard1.3;netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0 + net40;net45;net46;netstandard1.3;netstandard2.0;netcoreapp2.1;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 $(DefineConstants);NETSTANDARD - + $(DefineConstants);ASYNCENUMERABLE diff --git a/test/System.Linq.Dynamic.Core.Tests.Net6/System.Linq.Dynamic.Core.Tests.Net6.csproj b/test/System.Linq.Dynamic.Core.Tests.Net6/System.Linq.Dynamic.Core.Tests.Net6.csproj index 9ac387664..2bbc533b5 100644 --- a/test/System.Linq.Dynamic.Core.Tests.Net6/System.Linq.Dynamic.Core.Tests.Net6.csproj +++ b/test/System.Linq.Dynamic.Core.Tests.Net6/System.Linq.Dynamic.Core.Tests.Net6.csproj @@ -7,7 +7,6 @@ True ../../src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.snk false - $(DefineConstants);NETCOREAPP;EFCORE;EFCORE_3X;NETCOREAPP3_1 diff --git a/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj b/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj new file mode 100644 index 000000000..f36383144 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj @@ -0,0 +1,57 @@ + + + + net8.0 + System.Linq.Dynamic.Core.Tests + full + True + ../../src/System.Linq.Dynamic.Core/System.Linq.Dynamic.Core.snk + false + enable + $(DefineConstants);NETCOREAPP;EFCORE;EFCORE_3X;NETCOREAPP3_1 + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.FormattableString.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.FormattableString.cs index 478054cf9..9488dae58 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.FormattableString.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.FormattableString.cs @@ -139,6 +139,7 @@ public async Task Entities_LongCountAsync_Predicate_Args_FS() Assert.Equal(expected, result); } + [Fact(Skip = "not supported")] public void Entities_TakeWhile_FS() { //Arrange diff --git a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs index 9dbffb020..cc7973e6d 100644 --- a/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs +++ b/test/System.Linq.Dynamic.Core.Tests/EntitiesTests.TakeWhile.cs @@ -6,6 +6,7 @@ namespace System.Linq.Dynamic.Core.Tests public partial class EntitiesTests { // Not supported : https://msdn.microsoft.com/en-in/library/bb738474%28en-us%29.aspx + [Fact(Skip = "not supported")] public void Entities_TakeWhile() { //Arrange diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index aa5ad8b9d..af42df757 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -56,7 +56,7 @@ public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsExcepti // Assert var parseException = action.Should().Throw(); - parseException.Which.InnerException!.Message.Should().Contain("Insufficient hexadecimal digits"); + parseException.Which.InnerException!.Message.Should().Contain("hexadecimal digits"); parseException.Which.StackTrace.Should().Contain("at System.Linq.Dynamic.Core.Parser.StringParser.ParseString(String s) in ").And.Contain("System.Linq.Dynamic.Core\\Parser\\StringParser.cs:line "); } diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs index 0e48885f8..8e963f715 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Where.cs @@ -150,7 +150,7 @@ public void Where_Dynamic_Exceptions() Assert.Throws(() => qry.Where("Id=123")); Assert.Throws(() => DynamicQueryableExtensions.Where(null, "Id=1")); - Assert.Throws(() => qry.Where((string)null)); + Assert.Throws(() => qry.Where((string?)null)); Assert.Throws(() => qry.Where("")); Assert.Throws(() => qry.Where(" ")); } From 166eada8d9628fdb0d12e73b96091e87da18ed5d Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 20 Nov 2023 16:17:05 +0100 Subject: [PATCH 013/153] v1.3.7 --- CHANGELOG.md | 3 +++ Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 822bd6e38..7feaa71a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v1.3.7 (20 November 2023) +- [#756](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/756) - Add support for .NET 8 + Microsoft.EntityFrameworkCore 8 [feature] contributed by [StefH](https://github.com/StefH) + # v1.3.6 (19 November 2023) - [#750](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/750) - DynamicLinqTypeAttribute can now also be defined on an interface [feature] contributed by [StefH](https://github.com/StefH) - [#755](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/755) - Fix KeywordsHelper [bug] contributed by [StefH](https://github.com/StefH) diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index cff730338..76e340bf9 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.6 +SET version=v1.3.7 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index 80460ce54..4fe8065c5 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 6 + 7 \ No newline at end of file From 59e029becbf6ac561846866d3789a5111acc5f45 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Tue, 28 Nov 2023 17:17:41 +0100 Subject: [PATCH 014/153] Update function argument parsing for strings (#758) --- .../Parser/ExpressionParser.cs | 12 ++-- .../DynamicExpressionParserTests.cs | 59 +++++++++++++++++-- .../Helpers/Models/User.cs | 2 + 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 709622d0a..88f498ad4 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -914,6 +914,13 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) } } + // While the next token is also a string, keep concatenating these strings and get next token + while (_textParser.CurrentToken.Id == TokenId.StringLiteral) + { + stringValue += _textParser.CurrentToken.Text; + _textParser.NextToken(); + } + return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue); } @@ -2188,11 +2195,6 @@ private Expression[] ParseArguments() _textParser.NextToken(); } - //if (argList.OfType().Count() > 1) - //{ - // throw ParseError(_textParser.CurrentToken.Pos, Res.OutVariableSingleRequired); - //} - return argList.ToArray(); } diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 9dea54f57..82d987c15 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -262,6 +262,11 @@ public static class StaticHelper { return Guid.NewGuid(); } + + public static string Filter(string filter) + { + return filter; + } } public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider @@ -1379,14 +1384,58 @@ public void DynamicExpressionParser_ParseLambda_Operator_Less_Greater_With_Guids // Act var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); - var guidLambda = lambda as Expression>; - Assert.NotNull(guidLambda); + var guidLambda = (Expression>)lambda; - var del = lambda.Compile(); - var result = (Guid)del.DynamicInvoke(user); + var del = guidLambda.Compile(); + var result = (Guid?)del.DynamicInvoke(user); // Assert - Assert.Equal(anotherId, result); + result.Should().Be(anotherId); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ExpressionString() + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + var user = new User(); + + // Act : char + var expressionTextChar = "StaticHelper.Filter(\"C == 'x'\")"; + var lambdaChar = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextChar, user); + var funcChar = (Expression>)lambdaChar; + + var delegateChar = funcChar.Compile(); + var resultChar = (string?)delegateChar.DynamicInvoke(user); + + // Assert : int + resultChar.Should().Be("C == 'x'"); + + // Act : int + var expressionTextIncome = "StaticHelper.Filter(\"Income == 5\")"; + var lambdaIncome = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextIncome, user); + var funcIncome = (Expression>)lambdaIncome; + + var delegateIncome = funcIncome.Compile(); + var resultIncome = (string?)delegateIncome.DynamicInvoke(user); + + // Assert : int + resultIncome.Should().Be("Income == 5"); + + // Act : string + var expressionTextUserName = "StaticHelper.Filter(\"UserName == \"\"x\"\"\")"; + var lambdaUserName = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextUserName, user); + var funcUserName = (Expression>)lambdaUserName; + + var delegateUserName = funcUserName.Compile(); + var resultUserName = (string?)delegateUserName.DynamicInvoke(user); + + // Assert : string + resultUserName.Should().Be(@"UserName == ""x"""""""); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs index b758d408e..8b6bc5a90 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs @@ -18,6 +18,8 @@ public class User public int Income { get; set; } + public char C { get; set; } + public UserProfile Profile { get; set; } public UserState State { get; set; } From 2aecd4b7da2e713d3aef13d869cc16e64e4e8e86 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 13 Jan 2024 11:37:11 +0100 Subject: [PATCH 015/153] Update function argument parsing for strings (part 2) (#760) * Update function argument parsing for strings (part 2) * . * move test classes * ... * ok? * 8-preview-04 --- System.Linq.Dynamic.Core.sln.DotSettings | 1 + src/Directory.Build.props | 2 +- .../Parser/ExpressionParser.cs | 25 ++-- .../Parser/StringParser.cs | 81 +++++++----- .../DynamicExpressionParserTests.cs | 121 ++++++++---------- .../Helpers/Models/User.cs | 6 + .../Parser/StringParserTests.cs | 2 +- .../CustomClassWithStaticMethod.cs | 7 + .../TestClasses/StaticHelper.cs | 73 +++++++++++ .../TestClasses/StaticHelperSqlExpression.cs | 10 ++ .../TestClasses/TestCustomTypeProvider.cs | 55 ++++++++ version.xml | 2 +- 12 files changed, 271 insertions(+), 114 deletions(-) create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs diff --git a/System.Linq.Dynamic.Core.sln.DotSettings b/System.Linq.Dynamic.Core.sln.DotSettings index bc3691ece..fe4baceec 100644 --- a/System.Linq.Dynamic.Core.sln.DotSettings +++ b/System.Linq.Dynamic.Core.sln.DotSettings @@ -6,4 +6,5 @@ True True True + True True \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props index bc2081c3e..e34f3e2aa 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -7,7 +7,7 @@ Copyright © ZZZ Projects en-us true - 11 + latest enable logo.png PackageReadme.md diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 88f498ad4..7e81a3eab 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -889,25 +889,26 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) { _textParser.ValidateToken(TokenId.StringLiteral); - var stringValue = StringParser.ParseString(_textParser.CurrentToken.Text); + var text = _textParser.CurrentToken.Text; + var parsedStringValue = StringParser.ParseString(_textParser.CurrentToken.Text); if (_textParser.CurrentToken.Text[0] == '\'') { - if (stringValue.Length > 1) + if (parsedStringValue.Length > 1) { throw ParseError(Res.InvalidCharacterLiteral); } _textParser.NextToken(); - return ConstantExpressionHelper.CreateLiteral(stringValue[0], stringValue); + return ConstantExpressionHelper.CreateLiteral(parsedStringValue[0], parsedStringValue); } _textParser.NextToken(); - if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && stringValue.Length > 2 && stringValue.Contains('.')) + if (_parsingConfig.SupportCastingToFullyQualifiedTypeAsString && !forceParseAsString && parsedStringValue.Length > 2 && parsedStringValue.Contains('.')) { // Try to resolve this string as a type - var type = _typeFinder.FindTypeByName(stringValue, null, false); + var type = _typeFinder.FindTypeByName(parsedStringValue, null, false); if (type is { }) { return type; @@ -917,11 +918,13 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) // While the next token is also a string, keep concatenating these strings and get next token while (_textParser.CurrentToken.Id == TokenId.StringLiteral) { - stringValue += _textParser.CurrentToken.Text; + text += _textParser.CurrentToken.Text; _textParser.NextToken(); } - - return ConstantExpressionHelper.CreateLiteral(stringValue, stringValue); + + parsedStringValue = StringParser.ParseStringAndReplaceDoubleQuotes(text, _textParser.CurrentToken.Pos); + + return ConstantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); } private Expression ParseIntegerLiteral() @@ -2170,15 +2173,19 @@ private Expression[] ParseArgumentList() { _textParser.ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); _textParser.NextToken(); - Expression[] args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + + var args = _textParser.CurrentToken.Id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + _textParser.ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); _textParser.NextToken(); + return args; } private Expression[] ParseArguments() { var argList = new List(); + while (true) { var argumentExpression = ParseOutKeyword(); diff --git a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs index 3f22573d9..18dcb2b2a 100644 --- a/src/System.Linq.Dynamic.Core/Parser/StringParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/StringParser.cs @@ -2,40 +2,59 @@ using System.Linq.Dynamic.Core.Exceptions; using System.Text.RegularExpressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +/// +/// Parse a Double and Single Quoted string. +/// Some parts of the code is based on https://github.com/zzzprojects/Eval-Expression.NET +/// +internal static class StringParser { - /// - /// Parse a Double and Single Quoted string. - /// Some parts of the code is based on https://github.com/zzzprojects/Eval-Expression.NET - /// - internal static class StringParser + private const string Pattern = @""""""; + private const string Replacement = "\""; + + public static string ParseString(string s, int pos = default) { - public static string ParseString(string s) + if (s == null || s.Length < 2) + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringLength, s, 2), pos); + } + + if (s[0] != '"' && s[0] != '\'') + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringQuoteCharacter), pos); + } + + char quote = s[0]; // This can be single or a double quote + if (s.Last() != quote) + { + throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, s.Length, s), pos); + } + + try + { + return Regex.Unescape(s.Substring(1, s.Length - 2)); + } + catch (Exception ex) + { + throw new ParseException(ex.Message, pos, ex); + } + } + + public static string ParseStringAndReplaceDoubleQuotes(string s, int pos) + { + return ReplaceDoubleQuotes(ParseString(s, pos), pos); + } + + private static string ReplaceDoubleQuotes(string s, int pos) + { + try + { + return Regex.Replace(s, Pattern, Replacement); + } + catch (Exception ex) { - if (s == null || s.Length < 2) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringLength, s, 2), 0); - } - - if (s[0] != '"' && s[0] != '\'') - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.InvalidStringQuoteCharacter), 0); - } - - char quote = s[0]; // This can be single or a double quote - if (s.Last() != quote) - { - throw new ParseException(string.Format(CultureInfo.CurrentCulture, Res.UnexpectedUnclosedString, s.Length, s), s.Length); - } - - try - { - return Regex.Unescape(s.Substring(1, s.Length - 2)); - } - catch (Exception ex) - { - throw new ParseException(ex.Message, 0, ex); - } + throw new ParseException(ex.Message, pos, ex); } } } \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs index 82d987c15..fffdcca40 100644 --- a/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/DynamicExpressionParserTests.cs @@ -6,7 +6,6 @@ using System.Linq.Dynamic.Core.Tests.TestHelpers; using System.Linq.Expressions; using System.Reflection; -using System.Runtime.CompilerServices; using FluentAssertions; using Moq; using NFluent; @@ -77,11 +76,6 @@ public class ComplexParseLambda3Result public int TotalIncome { get; set; } } - public class CustomClassWithStaticMethod - { - public static int GetAge(int x) => x; - } - public class CustomClassWithMethod { public int GetAge(int x) => x; @@ -121,7 +115,7 @@ public CustomTextClass(string origin) public static implicit operator string(CustomTextClass customTextValue) { - return customTextValue?.Origin; + return customTextValue.Origin; } public static implicit operator CustomTextClass(string origin) @@ -256,67 +250,6 @@ public override string ToString() } } - public static class StaticHelper - { - public static Guid? GetGuid(string name) - { - return Guid.NewGuid(); - } - - public static string Filter(string filter) - { - return filter; - } - } - - public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider - { - private HashSet _customTypes; - - public virtual HashSet GetCustomTypes() - { - if (_customTypes != null) - { - return _customTypes; - } - - _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })) - { - typeof(CustomClassWithStaticMethod), - typeof(StaticHelper) - }; - return _customTypes; - } - - public Dictionary> GetExtensionMethods() - { - var types = GetCustomTypes(); - - var list = new List>(); - - foreach (var type in types) - { - var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) - .Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList(); - - extensionMethods.ForEach(x => list.Add(new Tuple(x.GetParameters()[0].ParameterType, x))); - } - - return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList()); - } - - public Type ResolveType(string typeName) - { - return Type.GetType(typeName); - } - - public Type ResolveTypeBySimpleName(string typeName) - { - var assemblies = AppDomain.CurrentDomain.GetAssemblies(); - return ResolveTypeBySimpleName(assemblies, typeName); - } - } - [Fact] public void DynamicExpressionParser_ParseLambda_UseParameterizedNamesInDynamicQuery_false_String() { @@ -1405,7 +1338,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var user = new User(); // Act : char - var expressionTextChar = "StaticHelper.Filter(\"C == 'x'\")"; + var expressionTextChar = "StaticHelper.Filter(\"C == 'c'\")"; var lambdaChar = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionTextChar, user); var funcChar = (Expression>)lambdaChar; @@ -1413,7 +1346,7 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var resultChar = (string?)delegateChar.DynamicInvoke(user); // Assert : int - resultChar.Should().Be("C == 'x'"); + resultChar.Should().Be("C == 'c'"); // Act : int var expressionTextIncome = "StaticHelper.Filter(\"Income == 5\")"; @@ -1435,7 +1368,53 @@ public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_Expressio var resultUserName = (string?)delegateUserName.DynamicInvoke(user); // Assert : string - resultUserName.Should().Be(@"UserName == ""x"""""""); + resultUserName.Should().Be(@"UserName == ""x"""); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression1String() + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + var user = new User(); + + // Act + var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = 5"""", """"""""))"", """"))"; + var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); + var func = (Expression>)lambda; + + var compile = func.Compile(); + var result = (bool?)compile.DynamicInvoke(user); + + // Assert + result.Should().Be(false); + } + + [Fact] + public void DynamicExpressionParser_ParseLambda_CustomType_Method_With_ComplexExpression2String() + { + // Arrange + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + var user = new User(); + + // Act + var expressionText = @"StaticHelper.In(Id, StaticHelper.SubSelect(""Identity"", ""LegalPerson"", ""StaticHelper.In(ParentId, StaticHelper.SubSelect(""""LegalPersonId"""", """"PointSiteTD"""", """"Identity = "" + StaticHelper.ToExpressionString(StaticHelper.Get(""CurrentPlace""), 2) + """""", """"""""))"", """"))"; + var lambda = DynamicExpressionParser.ParseLambda(config, typeof(User), null, expressionText, user); + var func = (Expression>)lambda; + + var compile = func.Compile(); + var result = (bool?)compile.DynamicInvoke(user); + + // Assert + result.Should().Be(false); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs index 8b6bc5a90..d84f5eaab 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Helpers/Models/User.cs @@ -6,6 +6,12 @@ public class User { public Guid Id { get; set; } + public Guid? ParentId { get; set; } + + public Guid? LegalPersonId { get; set; } + + public Guid? PointSiteTD { get; set; } + public SnowflakeId SnowflakeId { get; set; } public string UserName { get; set; } diff --git a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs index af42df757..cc8f782dd 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Parser/StringParserTests.cs @@ -58,7 +58,7 @@ public void StringParser_With_UnexpectedUnrecognizedEscapeSequence_ThrowsExcepti parseException.Which.InnerException!.Message.Should().Contain("hexadecimal digits"); - parseException.Which.StackTrace.Should().Contain("at System.Linq.Dynamic.Core.Parser.StringParser.ParseString(String s) in ").And.Contain("System.Linq.Dynamic.Core\\Parser\\StringParser.cs:line "); + parseException.Which.StackTrace.Should().Contain("at System.Linq.Dynamic.Core.Parser.StringParser.ParseString(String s, Int32 pos) in ").And.Contain("System.Linq.Dynamic.Core\\Parser\\StringParser.cs:line "); } [Theory] diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs new file mode 100644 index 000000000..ab6bad96e --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/CustomClassWithStaticMethod.cs @@ -0,0 +1,7 @@ +namespace System.Linq.Dynamic.Core.Tests +{ + public class CustomClassWithStaticMethod + { + public static int GetAge(int x) => x; + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs new file mode 100644 index 000000000..464fcdcd5 --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelper.cs @@ -0,0 +1,73 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Tests +{ + public static class StaticHelper + { + public static Guid? GetGuid(string name) + { + return Guid.NewGuid(); + } + + public static string Filter(string filter) + { + return filter; + } + + public static StaticHelperSqlExpression SubSelect(string columnName, string objectClassName, string? filter, string order) + { + Expression>? expFilter = null; + + if (filter != null) + { + var config = new ParsingConfig + { + CustomTypeProvider = new TestCustomTypeProvider() + }; + + expFilter = DynamicExpressionParser.ParseLambda(config, true, filter); // Failed Here! + } + + return new StaticHelperSqlExpression + { + Filter = expFilter + }; + } + + public static bool In(Guid? value, StaticHelperSqlExpression expression) + { + return value != Guid.Empty; + } + + public static Guid First(StaticHelperSqlExpression staticHelperSqlExpression) + { + return Guid.NewGuid(); + } + + public static string ToExpressionString(Guid? value, int subQueryLevel) + { + if (value == null) + { + return "NULL"; + } + + var quote = GetQuote(subQueryLevel); + return $"Guid.Parse({quote}{value}{quote})"; + } + + public static Guid Get(string settingName) + { + return Guid.NewGuid(); + } + + private static string GetQuote(int subQueryLevel) + { + var quoteCount = (int)Math.Pow(2, subQueryLevel - 1); + + //var quote = string.Concat(Enumerable.Repeat("\"", quoteCount)); + //return quote; + return new string('"', quoteCount); + } + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs new file mode 100644 index 000000000..d381a240f --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/StaticHelperSqlExpression.cs @@ -0,0 +1,10 @@ +using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using System.Linq.Expressions; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class StaticHelperSqlExpression + { + public Expression>? Filter { get; set; } + } +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs new file mode 100644 index 000000000..ccd6be9ef --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/TestClasses/TestCustomTypeProvider.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Linq.Dynamic.Core.CustomTypeProviders; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace System.Linq.Dynamic.Core.Tests +{ + public class TestCustomTypeProvider : AbstractDynamicLinqCustomTypeProvider, IDynamicLinkCustomTypeProvider + { + private HashSet? _customTypes; + + public virtual HashSet GetCustomTypes() + { + if (_customTypes != null) + { + return _customTypes; + } + + _customTypes = new HashSet(FindTypesMarkedWithDynamicLinqTypeAttribute(new[] { GetType().GetTypeInfo().Assembly })) + { + typeof(CustomClassWithStaticMethod), + typeof(StaticHelper) + }; + return _customTypes; + } + + public Dictionary> GetExtensionMethods() + { + var types = GetCustomTypes(); + + var list = new List>(); + + foreach (var type in types) + { + var extensionMethods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.IsDefined(typeof(ExtensionAttribute), false)).ToList(); + + extensionMethods.ForEach(x => list.Add(new Tuple(x.GetParameters()[0].ParameterType, x))); + } + + return list.GroupBy(x => x.Item1, tuple => tuple.Item2).ToDictionary(key => key.Key, methods => methods.ToList()); + } + + public Type ResolveType(string typeName) + { + return Type.GetType(typeName)!; + } + + public Type ResolveTypeBySimpleName(string typeName) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + return ResolveTypeBySimpleName(assemblies, typeName)!; + } + } +} \ No newline at end of file diff --git a/version.xml b/version.xml index 4fe8065c5..9f0743a98 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 7 + 8-preview-04 \ No newline at end of file From 7050d72fdc4f9b8991f86e434f5526ee04ef990e Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 20 Jan 2024 09:33:51 +0100 Subject: [PATCH 016/153] v1.3.8 --- CHANGELOG.md | 18 +++++++----------- Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7feaa71a5..ce3248e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +# v1.3.8 (20 January 2024) +- [#758](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/758) - Update function argument parsing for strings [bug] contributed by [StefH](https://github.com/StefH) +- [#760](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/760) - Update function argument parsing for strings (part 2) [bug] contributed by [StefH](https://github.com/StefH) +- [#757](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/757) - ParseException after update from v.1.0.8.18 to 1.0.9.0 or any newer (till 1.3.7) [bug] +- [#759](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/759) - Still yet ParseException after update from v.1.0.8.18 to 1.0.9.0 or any newer (till 1.3.7) [bug] + # v1.3.7 (20 November 2023) - [#756](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/756) - Add support for .NET 8 + Microsoft.EntityFrameworkCore 8 [feature] contributed by [StefH](https://github.com/StefH) @@ -56,7 +62,6 @@ - [#669](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/669) - Methods should only be callable on predefined types [security] contributed by [StefH](https://github.com/StefH) - [#671](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/671) - Add support for DateOnly and TimeOnly [feature] contributed by [StefH](https://github.com/StefH) - [#672](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/672) - Fixed ExpressionParser when WrappedValue-string is used for equals-operator for Enum [bug] contributed by [StefH](https://github.com/StefH) -- [#674](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/674) - Fixed ExpressionParser when WrappedValue-string is used for equals-operator for Enum [refactor] contributed by [neilbgr](https://github.com/neilbgr) - [#676](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/676) - Change default setting for PrioritizePropertyOrFieldOverTheType to 'true'. [bug] contributed by [StefH](https://github.com/StefH) - [#660](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/660) - Question about security [security] - [#667](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/667) - DateOnly support [feature] @@ -196,7 +201,6 @@ - [#455](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/455) - Ensure action delegate allows call to void methods contributed by [glopesdev](https://github.com/glopesdev) - [#480](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/480) - Fix DynamicIndex implementation [bug] contributed by [StefH](https://github.com/StefH) - [#481](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/481) - Xamarin fix Enum [bug] contributed by [StefH](https://github.com/StefH) -- [#484](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/484) - Implement support for anonymous types as dynamic objects [bug] contributed by [hazzik](https://github.com/hazzik) - [#448](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/448) - Dynamic.DynamicIndex is exposed in the expression [bug] - [#479](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/479) - Xamarin.Forms - DynamicExpressionParser.ParseLambda fails when comparing enum properties by their int value [bug] @@ -376,9 +380,6 @@ # 1.0.11.0 (28 February 2019) - [#249](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/249) - Add support for OfType, Is, As and Cast [feature] contributed by [StefH](https://github.com/StefH) - [#250](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/250) - Wrap all constant expressions to fix Parameterized SQL (#247) [bug, feature] contributed by [StefH](https://github.com/StefH) -- [#251](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/251) - Add NetCoreApp target & include DefaultDynamicLinqCustomTypeProvider [feature] contributed by [StefH](https://github.com/StefH) -- [#253](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/253) - OfType Function contributed by [StefH](https://github.com/StefH) -- [#254](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/254) - Resolve types by simple name #252 [feature] contributed by [StefH](https://github.com/StefH) - [#255](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/255) - Fix SonarScanner in build [bug] contributed by [StefH](https://github.com/StefH) - [#247](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/247) - Parameterized SQL doesn't work for Contains, StartsWith, and EndsWith - [#248](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/248) - Add IQueryable.OfType support to ExpressionParser. @@ -411,24 +412,19 @@ - [#237](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/237) - Performance Fix [bug] contributed by [StefH](https://github.com/StefH) - [#71](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/71) - Issue: Increase code-coverage [feature] - [#119](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/119) - Feature: How to keep parameter input name of query [feature] -- [#145](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/145) - Question : Performance and 'System.IO.FileNotFoundException' in System.Private.CoreLib.dll - [#152](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/152) - Multiple assemblies with equivalent identity have been imported - [#179](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/179) - Feature: Implement SourceLink - [#184](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/184) - Feature: Generate Parameterized SQL [feature] - [#204](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/204) - Cannot GroupJoin when source is Linq-To-Entities - [#209](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/209) - Feature: use Azure Pipelines for building - [#215](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/215) - Issue: DynamicClassFactory fails to create dynamic type without properties [bug] -- [#234](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/234) - Does this support Json data -- [#236](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/236) - Massive performance hit when upgrading from 1.0.8.18 to 1.0.9 +- [#236](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/236) - Massive performance hit when upgrading from 1.0.8.18 to 1.0.9 [bug] # 1.0.9.0 (19 October 2018) - [#208](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/208) - Fix New() support for Type + Fix GroupJoin() not working when using Linq-To-Entities (2) contributed by [StefH](https://github.com/StefH) - [#136](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/136) - Expressions on dynamic objects -- [#147](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/147) - Question: Making some queries dynamic possible - [#173](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/173) - Error when trying to access an object declared on another lambda -- [#181](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/181) - Question: QueryValidator - [#199](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/199) - Feature: Add EvaluateGroupByAtDatabase logic to Join and GroupJoin -- [#203](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/203) - How to query complex entities - [#205](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/205) - Documentation of supported operations - [#206](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/206) - Issue: new() expression cannot handle complex types diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index 76e340bf9..9d6fc41cd 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.7 +SET version=v1.3.8 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index 9f0743a98..07e54a693 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 8-preview-04 + 8 \ No newline at end of file From 6a70951c7e6f08c2e482261d8ad7bedfeb48d850 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 20 Jan 2024 14:14:40 +0100 Subject: [PATCH 017/153] Add unit tests for Any (#761) --- .../QueryableTests.Any.cs | 318 +++++++++++------- 1 file changed, 201 insertions(+), 117 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs index 32feb2ee2..afbb48fd6 100644 --- a/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs +++ b/test/System.Linq.Dynamic.Core.Tests/QueryableTests.Any.cs @@ -1,154 +1,238 @@ using System.Collections.Generic; using System.Linq.Dynamic.Core.Tests.Helpers.Models; +using FluentAssertions; using Xunit; -namespace System.Linq.Dynamic.Core.Tests +namespace System.Linq.Dynamic.Core.Tests; + +public partial class QueryableTests { - public partial class QueryableTests + [Fact] + public void Any() { - [Fact] - public void Any() - { - //Arrange - IQueryable testListFull = User.GenerateSampleModels(100).AsQueryable(); - IQueryable testListOne = User.GenerateSampleModels(1).AsQueryable(); - IQueryable testListNone = User.GenerateSampleModels(0).AsQueryable(); - - //Act - var resultFull = testListFull.Any(); - var resultOne = testListOne.Any(); - var resultNone = testListNone.Any(); - - //Assert - Assert.True(resultFull); - Assert.True(resultOne); - Assert.False(resultNone); - } + //Arrange + IQueryable testListFull = User.GenerateSampleModels(100).AsQueryable(); + IQueryable testListOne = User.GenerateSampleModels(1).AsQueryable(); + IQueryable testListNone = User.GenerateSampleModels(0).AsQueryable(); + + //Act + var resultFull = testListFull.Any(); + var resultOne = testListOne.Any(); + var resultNone = testListNone.Any(); + + //Assert + Assert.True(resultFull); + Assert.True(resultOne); + Assert.False(resultNone); + } - [Fact] - public void Any_Predicate() - { - //Arrange - var queryable = User.GenerateSampleModels(100).AsQueryable(); + [Fact] + public void Any_Predicate() + { + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); - //Act - bool expected = queryable.Any(u => u.Income > 50); - bool result = queryable.Any("Income > 50"); + //Act + bool expected = queryable.Any(u => u.Income > 50); + bool result = queryable.Any("Income > 50"); - //Assert - Assert.Equal(expected, result); - } + //Assert + Assert.Equal(expected, result); + } - [Fact] - public void Any_Predicate_WithArgs() - { - const int value = 50; + [Fact] + public void Any_Predicate_WithArgs() + { + const int value = 50; - //Arrange - var queryable = User.GenerateSampleModels(100).AsQueryable(); + //Arrange + var queryable = User.GenerateSampleModels(100).AsQueryable(); - //Act - bool expected = queryable.Any(u => u.Income > value); - bool result = queryable.Any("Income > @0", value); + //Act + bool expected = queryable.Any(u => u.Income > value); + bool result = queryable.Any("Income > @0", value); - //Assert - Assert.Equal(expected, result); - } + //Assert + Assert.Equal(expected, result); + } - [Fact] - public void Any_Dynamic_Select() - { - // Arrange - IQueryable queryable = User.GenerateSampleModels(1).AsQueryable(); + [Fact] + public void Any_Dynamic_Select() + { + // Arrange + IQueryable queryable = User.GenerateSampleModels(1).AsQueryable(); - // Act - var expected = queryable.Select(x => x.Roles.Any()).ToArray(); - var result = queryable.Select("Roles.Any()").ToDynamicArray(); + // Act + var expected = queryable.Select(x => x.Roles.Any()).ToArray(); + var result = queryable.Select("Roles.Any()").ToDynamicArray(); - // Assert - Assert.Equal(expected, result); - } + // Assert + Assert.Equal(expected, result); + } - [Fact] - public void Any_Dynamic_Where() - { - const string search = "e"; + [Fact] + public void Any_Dynamic_Where() + { + const string search = "e"; - // Arrange - var testList = User.GenerateSampleModels(10); - var queryable = testList.AsQueryable(); + // Arrange + var testList = User.GenerateSampleModels(10); + var queryable = testList.AsQueryable(); - // Act - var expected = queryable.Where(u => u.Roles.Any(r => r.Name.Contains(search))).ToArray(); - var result = queryable.Where("Roles.Any(Name.Contains(@0))", search).ToArray(); + // Act + var expected = queryable.Where(u => u.Roles.Any(r => r.Name.Contains(search))).ToArray(); + var result = queryable.Where("Roles.Any(Name.Contains(@0))", search).ToArray(); - Assert.Equal(expected, result); - } + Assert.Equal(expected, result); + } - // https://dynamiclinq.codeplex.com/discussions/654313 - [Fact] - public void Any_Dynamic_Where_Nested() - { - const string search = "a"; + // https://dynamiclinq.codeplex.com/discussions/654313 + [Fact] + public void Any_Dynamic_Where_Nested() + { + const string search = "a"; - // Arrange - var testList = User.GenerateSampleModels(10); - var queryable = testList.AsQueryable(); + // Arrange + var testList = User.GenerateSampleModels(10); + var queryable = testList.AsQueryable(); - // Act - var expected = queryable.Where(u => u.Roles.Any(r => r.Permissions.Any(p => p.Name.Contains(search)))).ToArray(); - var result = queryable.Where("Roles.Any(Permissions.Any(Name.Contains(@0)))", search).ToArray(); + // Act + var expected = queryable.Where(u => u.Roles.Any(r => r.Permissions.Any(p => p.Name.Contains(search)))).ToArray(); + var result = queryable.Where("Roles.Any(Permissions.Any(Name.Contains(@0)))", search).ToArray(); - Assert.Equal(expected, result); - } + Assert.Equal(expected, result); + } - // http://stackoverflow.com/questions/30846189/nested-any-in-is-not-working-in-dynamic-linq - [Fact] - public void Any_Dynamic_Where_Nested2() + // http://stackoverflow.com/questions/30846189/nested-any-in-is-not-working-in-dynamic-linq + [Fact] + public void Any_Dynamic_Where_Nested2() + { + // arrange + var list = new List { - // arrange - var list = new List - { - new A {Bs = new List {new B {A = new A(), Cs = new List {new C {B = new B()}}}}} - }; - var queryable = list.AsQueryable(); - - // act : 1 - var result1 = queryable.Where("(Name = \"\") && (Bs.Any(Cs.Any()))").ToList(); - var expected1 = queryable.Where(a => a.Name == "" && a.Bs.Any(b => b.Cs.Any())); - Assert.Equal(expected1, result1); - - // act : 2 - var result2 = queryable.Where("(Bs.Any(Cs.Any())) && (Name = \"\")").ToList(); - var expected2 = queryable.Where(a => a.Bs.Any(b => b.Cs.Any() && a.Name == "")); - Assert.Equal(expected2, result2); - } + new A {Bs = new List {new B {A = new A(), Cs = new List {new C {B = new B()}}}}} + }; + var queryable = list.AsQueryable(); + + // act : 1 + var result1 = queryable.Where("(Name = \"\") && (Bs.Any(Cs.Any()))").ToList(); + var expected1 = queryable.Where(a => a.Name == "" && a.Bs.Any(b => b.Cs.Any())); + Assert.Equal(expected1, result1); + + // act : 2 + var result2 = queryable.Where("(Bs.Any(Cs.Any())) && (Name = \"\")").ToList(); + var expected2 = queryable.Where(a => a.Bs.Any(b => b.Cs.Any() && a.Name == "")); + Assert.Equal(expected2, result2); + } - class A + // #540 + [Fact] + public void Any_SubList() + { + var organizations = new List { - public string Name { get; set; } - public IList Bs + new() + { + Code = "OrganizationA", + Sites = new List + { + new() + { + Code = "SiteNorth1" + }, + new() + { + Code = "SiteNorth2" + } + } + }, + new() + { + Code = "OrganizationB", + Sites = new List + { + new() + { + Code = "SiteNorth3" + }, + new() + { + Code = "SiteNorth4" + } + } + }, + new() { - get { return bs; } - set { bs = value; } + Code = "OrganizationC", + Sites = new List + { + new() + { + Code = "SiteNorth1" + }, + new() + { + Code = "SiteNorth5" + } + } } - private IList bs = new List(0); - } + }.AsQueryable(); + + var siteFilters = new List { "SiteNorth1", "SiteNorth5" }; + + // Working by accessing from organizations with predicate params for an equal expression + var test2 = organizations.Where("Sites.Any(Code = @0)", siteFilters.First()).ToDynamicArray(); + test2.Should().HaveCount(2); + + // Working by accessing from organizations with classic LINQ + var test3 = organizations.Where(_ => _.Sites.Any(_ => siteFilters.Contains(_.Code))).ToDynamicArray(); + test3.Should().HaveCount(2); + + // Working by accessing from organizations with string predicate + var test5 = organizations.Where("Sites.Any(Code in (\"SiteNorth1\", \"SiteNorth5\"))").ToDynamicArray(); + test5.Should().HaveCount(2); + + // Not working : error : System.Linq.Dynamic.Core.Exceptions.ParseException: '')' or ',' expected' + var test6 = organizations.Where("Sites.Any(Code in @0)", siteFilters).ToDynamicArray(); + test6.Should().HaveCount(2); + } + + public class Organization + { + public string Code { get; set; } - class B + public ICollection Sites { get; set; } + } + + public class Site + { + public string Code { get; set; } + } + + class A + { + public string Name { get; set; } + public IList Bs { - public A A { get; set; } - public IList Cs - { - get { return cs; } - set { cs = value; } - } - private IList cs = new List(0); + get { return bs; } + set { bs = value; } } + private IList bs = new List(0); + } - class C + class B + { + public A A { get; set; } + public IList Cs { - public B B { get; set; } + get { return cs; } + set { cs = value; } } + private IList cs = new List(0); + } + + class C + { + public B B { get; set; } } -} +} \ No newline at end of file From fadb2508a85216024138ec6ef5fc4dfedf9128f2 Mon Sep 17 00:00:00 2001 From: Travis Whidden Date: Mon, 22 Jan 2024 10:42:02 -0800 Subject: [PATCH 018/153] #764 - Introduce Sliding Cache to Constant Expression Helper (#765) * #764 - Introduce Sliding Cache to Constant Expression Helper * #764 Additional Tests, Missing TTL Update * #764 - Rename T1,T2 to TKey, TValue * #764 - Set cleanup time prior to enumeration to prevent multiple cleanup hits. * #764 - Code Review Changes implemented; ThreadSafeSlidingCache Tests Added * #764 - Move DefaultCleanupFrequency to internal static non generic class * #764 - Dropped Preprocessor Directive for TTL in ConstantExpressionHelper * #764 - Move ConstantExpressionHelper to Instance. Refactor feedback from Review * #764 - ConstantExpressionHelper Singleton Factory; Code Review Resolutions * #764 - Refactor Naming for SlidingCache; * #764 - PR Refactor Feedback --------- Co-authored-by: Travis Whidden --- .../Parser/ConstantExpressionHelper.cs | 45 +++-- .../Parser/ConstantExpressionHelperFactory.cs | 20 +++ .../Parser/ExpressionParser.cs | 6 +- .../Parser/ExpressionPromoter.cs | 4 +- .../Parser/NumberParser.cs | 34 ++-- src/System.Linq.Dynamic.Core/ParsingConfig.cs | 6 + .../Util/Cache/CacheConfig.cs | 27 +++ .../Util/Cache/CacheEntry.cs | 14 ++ .../Util/Cache/SlidingCache.cs | 154 ++++++++++++++++++ .../Util/Cache/SlidingCacheConstants.cs | 7 + .../Util/DateTimeUtils.cs | 12 ++ .../Util/TaskUtils.cs | 17 ++ .../Util/Cache/SlidingCacheTests.cs | 150 +++++++++++++++++ 13 files changed, 460 insertions(+), 36 deletions(-) create mode 100644 src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelperFactory.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/Cache/CacheEntry.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/DateTimeUtils.cs create mode 100644 src/System.Linq.Dynamic.Core/Util/TaskUtils.cs create mode 100644 test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs index 5b651651a..bae556f8e 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs @@ -1,29 +1,40 @@ -using System.Collections.Concurrent; +using System.Linq.Dynamic.Core.Util.Cache; +using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; -namespace System.Linq.Dynamic.Core.Parser +namespace System.Linq.Dynamic.Core.Parser; + +internal class ConstantExpressionHelper { - internal static class ConstantExpressionHelper + private readonly SlidingCache _expressions; + private readonly SlidingCache _literals; + + public ConstantExpressionHelper(ParsingConfig config) + { + var parsingConfig = Check.NotNull(config); + var useConfig = parsingConfig.ConstantExpressionCacheConfig ?? new CacheConfig(); + + _literals = new SlidingCache(useConfig); + _expressions = new SlidingCache(useConfig); + } + + public bool TryGetText(Expression expression, out string? text) { - private static readonly ConcurrentDictionary Expressions = new(); - private static readonly ConcurrentDictionary Literals = new(); + return _literals.TryGetValue(expression, out text); + } - public static bool TryGetText(Expression expression, out string? text) + public Expression CreateLiteral(object value, string text) + { + if (_expressions.TryGetValue(value, out var outputValue)) { - return Literals.TryGetValue(expression, out text); + return outputValue; } - public static Expression CreateLiteral(object value, string text) - { - if (!Expressions.ContainsKey(value)) - { - ConstantExpression constantExpression = Expression.Constant(value); + var constantExpression = Expression.Constant(value); - Expressions.TryAdd(value, constantExpression); - Literals.TryAdd(constantExpression, text); - } + _expressions.AddOrUpdate(value, constantExpression); + _literals.AddOrUpdate(constantExpression, text); - return Expressions[value]; - } + return constantExpression; } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelperFactory.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelperFactory.cs new file mode 100644 index 000000000..4a3b7ec31 --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelperFactory.cs @@ -0,0 +1,20 @@ +namespace System.Linq.Dynamic.Core.Parser; + +internal static class ConstantExpressionHelperFactory +{ + private static readonly object Lock = new(); + private static ConstantExpressionHelper? _instance; + + public static ConstantExpressionHelper GetInstance(ParsingConfig config) + { + if (_instance == null) + { + lock (Lock) + { + _instance ??= new ConstantExpressionHelper(config); + } + } + + return _instance; + } +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs index 7e81a3eab..74240e615 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionParser.cs @@ -45,6 +45,7 @@ public class ExpressionParser private ParameterExpression? _root; private Type? _resultType; private bool _createParameterCtor; + private ConstantExpressionHelper _constantExpressionHelper; /// /// Gets name for the `it` field. By default this is set to the KeyWord value "it". @@ -81,6 +82,7 @@ public ExpressionParser(ParameterExpression[]? parameters, string expression, ob _methodFinder = new MethodFinder(_parsingConfig, _expressionHelper); _typeFinder = new TypeFinder(_parsingConfig, _keywordsHelper); _typeConverterFactory = new TypeConverterFactory(_parsingConfig); + _constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(_parsingConfig); if (parameters != null) { @@ -900,7 +902,7 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) } _textParser.NextToken(); - return ConstantExpressionHelper.CreateLiteral(parsedStringValue[0], parsedStringValue); + return _constantExpressionHelper.CreateLiteral(parsedStringValue[0], parsedStringValue); } _textParser.NextToken(); @@ -924,7 +926,7 @@ private AnyOf ParseStringLiteral(bool forceParseAsString) parsedStringValue = StringParser.ParseStringAndReplaceDoubleQuotes(text, _textParser.CurrentToken.Pos); - return ConstantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); + return _constantExpressionHelper.CreateLiteral(parsedStringValue, parsedStringValue); } private Expression ParseIntegerLiteral() diff --git a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs index 67df36325..6c12ad7fa 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ExpressionPromoter.cs @@ -9,6 +9,7 @@ namespace System.Linq.Dynamic.Core.Parser public class ExpressionPromoter : IExpressionPromoter { private readonly NumberParser _numberParser; + private readonly ConstantExpressionHelper _constantExpressionHelper; /// /// Initializes a new instance of the class. @@ -17,6 +18,7 @@ public class ExpressionPromoter : IExpressionPromoter public ExpressionPromoter(ParsingConfig config) { _numberParser = new NumberParser(config); + _constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(config); } /// @@ -48,7 +50,7 @@ public ExpressionPromoter(ParsingConfig config) } else { - if (ConstantExpressionHelper.TryGetText(ce, out var text)) + if (_constantExpressionHelper.TryGetText(ce, out var text)) { Type target = TypeHelper.GetNonNullableType(type); object? value = null; diff --git a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs index 791e506c3..c8d7c5d1a 100644 --- a/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs +++ b/src/System.Linq.Dynamic.Core/Parser/NumberParser.cs @@ -16,6 +16,7 @@ public class NumberParser private static readonly char[] Qualifiers = { 'U', 'u', 'L', 'l', 'F', 'f', 'D', 'd', 'M', 'm' }; private static readonly char[] QualifiersHex = { 'U', 'u', 'L', 'l' }; private static readonly string[] QualifiersReal = { "F", "f", "D", "d", "M", "m" }; + private readonly ConstantExpressionHelper _constantExpressionHelper; private readonly CultureInfo _culture; @@ -26,6 +27,7 @@ public class NumberParser public NumberParser(ParsingConfig? config) { _culture = config?.NumberParseCulture ?? CultureInfo.InvariantCulture; + _constantExpressionHelper = ConstantExpressionHelperFactory.GetInstance(config ?? ParsingConfig.Default); } /// @@ -77,12 +79,12 @@ public Expression ParseIntegerLiteral(int tokenPosition, string text) { if (qualifier == "U" || qualifier == "u") { - return ConstantExpressionHelper.CreateLiteral((uint)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, text); } if (qualifier == "L" || qualifier == "l") { - return ConstantExpressionHelper.CreateLiteral((long)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((long)unsignedValue, text); } if (QualifiersReal.Contains(qualifier)) @@ -90,25 +92,25 @@ public Expression ParseIntegerLiteral(int tokenPosition, string text) return ParseRealLiteral(text, qualifier[0], false); } - return ConstantExpressionHelper.CreateLiteral(unsignedValue, text); + return _constantExpressionHelper.CreateLiteral(unsignedValue, text); } if (unsignedValue <= int.MaxValue) { - return ConstantExpressionHelper.CreateLiteral((int)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((int)unsignedValue, text); } if (unsignedValue <= uint.MaxValue) { - return ConstantExpressionHelper.CreateLiteral((uint)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((uint)unsignedValue, text); } if (unsignedValue <= long.MaxValue) { - return ConstantExpressionHelper.CreateLiteral((long)unsignedValue, text); + return _constantExpressionHelper.CreateLiteral((long)unsignedValue, text); } - return ConstantExpressionHelper.CreateLiteral(unsignedValue, text); + return _constantExpressionHelper.CreateLiteral(unsignedValue, text); } if (isHexadecimal || isBinary) @@ -135,7 +137,7 @@ public Expression ParseIntegerLiteral(int tokenPosition, string text) { if (qualifier == "L" || qualifier == "l") { - return ConstantExpressionHelper.CreateLiteral(value, text); + return _constantExpressionHelper.CreateLiteral(value, text); } if (QualifiersReal.Contains(qualifier)) @@ -148,10 +150,10 @@ public Expression ParseIntegerLiteral(int tokenPosition, string text) if (value <= int.MaxValue) { - return ConstantExpressionHelper.CreateLiteral((int)value, text); + return _constantExpressionHelper.CreateLiteral((int)value, text); } - return ConstantExpressionHelper.CreateLiteral(value, text); + return _constantExpressionHelper.CreateLiteral(value, text); } /// @@ -163,18 +165,18 @@ public Expression ParseRealLiteral(string text, char qualifier, bool stripQualif { case 'f': case 'F': - return ConstantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(float))!, text); + return _constantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(float))!, text); case 'm': case 'M': - return ConstantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(decimal))!, text); + return _constantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(decimal))!, text); case 'd': case 'D': - return ConstantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(double))!, text); + return _constantExpressionHelper.CreateLiteral(ParseNumber(stripQualifier ? text.Substring(0, text.Length - 1) : text, typeof(double))!, text); default: - return ConstantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(double))!, text); + return _constantExpressionHelper.CreateLiteral(ParseNumber(text, typeof(double))!, text); } } @@ -285,12 +287,12 @@ private Expression ParseAsBinary(int tokenPosition, string text, bool isNegative { if (RegexBinary32.IsMatch(text)) { - return ConstantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt32(text, 2), text); + return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt32(text, 2), text); } if (RegexBinary64.IsMatch(text)) { - return ConstantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt64(text, 2), text); + return _constantExpressionHelper.CreateLiteral((isNegative ? -1 : 1) * Convert.ToInt64(text, 2), text); } throw new ParseException(string.Format(_culture, Res.InvalidBinaryIntegerLiteral, text), tokenPosition); diff --git a/src/System.Linq.Dynamic.Core/ParsingConfig.cs b/src/System.Linq.Dynamic.Core/ParsingConfig.cs index 157aadd02..ec3a164a2 100644 --- a/src/System.Linq.Dynamic.Core/ParsingConfig.cs +++ b/src/System.Linq.Dynamic.Core/ParsingConfig.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq.Dynamic.Core.CustomTypeProviders; using System.Linq.Dynamic.Core.Parser; +using System.Linq.Dynamic.Core.Util.Cache; namespace System.Linq.Dynamic.Core; @@ -234,4 +235,9 @@ public IQueryableAnalyzer QueryableAnalyzer /// Default value is false. /// public bool DisallowNewKeyword { get; set; } = false; + + /// + /// Caches constant expressions to enhance performance. Periodic cleanup is performed to manage cache size, governed by this configuration. + /// + public CacheConfig? ConstantExpressionCacheConfig { get; set; } } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs new file mode 100644 index 000000000..d1a63a35c --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs @@ -0,0 +1,27 @@ +namespace System.Linq.Dynamic.Core.Util.Cache; + +/// +/// Cache Configuration Options +/// +public class CacheConfig +{ + /// + /// Sets a Time-To-Live (TTL) for items in the constant expression cache to prevent uncontrolled growth. + /// Items not accessed within this TTL will be expired, allowing garbage collection to reclaim the memory. + /// Default is 10 minutes. + /// + public TimeSpan TimeToLive { get; set; } = TimeSpan.FromMinutes(10); + + /// + /// Configures the minimum number of items required in the constant expression cache before triggering cleanup. + /// This prevents frequent cleanups, especially in caches with few items. + /// A default value of null implies that cleanup is always allowed to run, helping in timely removal of unused cache items. + /// + public int? MinItemsTrigger { get; set; } + + /// + /// Sets the frequency for running the cleanup process in the Constant Expression cache. + /// By default, cleanup occurs every 10 minutes. + /// + public TimeSpan CleanupFrequency { get; set; } = TimeSpan.FromMinutes(10); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/CacheEntry.cs b/src/System.Linq.Dynamic.Core/Util/Cache/CacheEntry.cs new file mode 100644 index 000000000..363546c1a --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/Cache/CacheEntry.cs @@ -0,0 +1,14 @@ +namespace System.Linq.Dynamic.Core.Util.Cache; + +internal struct CacheEntry where TValue : notnull +{ + public TValue Value { get; } + + public DateTime ExpirationTime { get; } + + public CacheEntry(TValue value, DateTime expirationTime) + { + Value = value; + ExpirationTime = expirationTime; + } +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs new file mode 100644 index 000000000..5697bc34b --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs @@ -0,0 +1,154 @@ +using System.Collections.Concurrent; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Dynamic.Core.Validation; + +namespace System.Linq.Dynamic.Core.Util.Cache; + +internal class SlidingCache where TKey : notnull where TValue : notnull +{ + private readonly ConcurrentDictionary> _cache; + private readonly TimeSpan _cleanupFrequency; + private readonly IDateTimeUtils _dateTimeProvider; + private readonly Action _deleteExpiredCachedItemsDelegate; + private readonly long? _minCacheItemsBeforeCleanup; + private DateTime _lastCleanupTime; + + /// + /// Sliding Thread Safe Cache + /// + /// The length of time any object would survive before being removed + /// Only look for expired objects over specific periods + /// + /// If defined, only allow the cleanup process after x number of cached items have + /// been stored + /// + /// + /// Provides the Time for the Caching object. Default will be created if not supplied. Used + /// for Testing classes + /// + public SlidingCache( + TimeSpan timeToLive, + TimeSpan? cleanupFrequency = null, + long? minCacheItemsBeforeCleanup = null, + IDateTimeUtils? dateTimeProvider = null) + { + _cache = new ConcurrentDictionary>(); + TimeToLive = timeToLive; + _minCacheItemsBeforeCleanup = minCacheItemsBeforeCleanup; + _cleanupFrequency = cleanupFrequency ?? SlidingCacheConstants.DefaultCleanupFrequency; + _deleteExpiredCachedItemsDelegate = Cleanup; + _dateTimeProvider = dateTimeProvider ?? new DateTimeUtils(); + // To prevent a scan on first call, set the last Cleanup to the current Provider time + _lastCleanupTime = _dateTimeProvider.UtcNow; + } + + /// + /// Sliding Thread Safe Cache + /// + /// + /// + public SlidingCache( + CacheConfig cashConfig, + IDateTimeUtils? dateTimeProvider = null) + { + _cache = new ConcurrentDictionary>(); + TimeToLive = cashConfig.TimeToLive; + _minCacheItemsBeforeCleanup = cashConfig.MinItemsTrigger; + _cleanupFrequency = cashConfig.CleanupFrequency; + _deleteExpiredCachedItemsDelegate = Cleanup; + _dateTimeProvider = dateTimeProvider ?? new DateTimeUtils(); + // To prevent a scan on first call, set the last Cleanup to the current Provider time + _lastCleanupTime = _dateTimeProvider.UtcNow; + } + + /// + /// Cache TTL value + /// + public TimeSpan TimeToLive { get; } + + /// + /// Provide the number of items in the cache + /// + public int Count => _cache.Count; + + /// + /// Add or update the item in the cache, at the same time update the expiration time + /// + /// + /// + public void AddOrUpdate(TKey key, TValue value) + { + Check.NotNull(key); + Check.NotNull(value); + + var expirationTime = _dateTimeProvider.UtcNow.Add(TimeToLive); + _cache[key] = new CacheEntry(value, expirationTime); + + CleanupIfNeeded(); + } + + /// + /// Attempt to get the value from the cache. This will extend the cache expiration time if the item is found + /// + /// Key + /// Value + /// + public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) + { + Check.NotNull(key); + + CleanupIfNeeded(); + + if (_cache.TryGetValue(key, out var valueAndExpiration)) + { + if (_dateTimeProvider.UtcNow <= valueAndExpiration.ExpirationTime) + { + value = valueAndExpiration.Value; + var newExpire = _dateTimeProvider.UtcNow.Add(TimeToLive); + _cache[key] = new CacheEntry(value, newExpire); + return true; + } + + // Remove expired item + _cache.TryRemove(key, out _); + } + + value = default; + return false; + } + + public bool Remove(TKey key) + { + Check.NotNull(key); + + var removed = _cache.TryRemove(key, out _); + CleanupIfNeeded(); + return removed; + } + + private void CleanupIfNeeded() + { + if (_dateTimeProvider.UtcNow - _lastCleanupTime > _cleanupFrequency + && (_minCacheItemsBeforeCleanup == null || + _cache.Count >= + _minCacheItemsBeforeCleanup) // Only cleanup if we have a minimum number of items in the cache. + ) + { + // Set here, so we don't have re-entry due to large collection enumeration. + _lastCleanupTime = _dateTimeProvider.UtcNow; + + TaskUtils.Run(_deleteExpiredCachedItemsDelegate); + } + } + + private void Cleanup() + { + foreach (var key in _cache.Keys) + { + if (_dateTimeProvider.UtcNow > _cache[key].ExpirationTime) + { + _cache.TryRemove(key, out _); + } + } + } +} diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs new file mode 100644 index 000000000..3dd8ee8bd --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs @@ -0,0 +1,7 @@ +namespace System.Linq.Dynamic.Core.Util.Cache; + +internal static class SlidingCacheConstants +{ + // Default cleanup frequency + public static readonly TimeSpan DefaultCleanupFrequency = TimeSpan.FromMinutes(10); +} diff --git a/src/System.Linq.Dynamic.Core/Util/DateTimeUtils.cs b/src/System.Linq.Dynamic.Core/Util/DateTimeUtils.cs new file mode 100644 index 000000000..6a61abf1f --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/DateTimeUtils.cs @@ -0,0 +1,12 @@ +namespace System.Linq.Dynamic.Core.Util +{ + internal interface IDateTimeUtils + { + DateTime UtcNow { get; } + } + + internal class DateTimeUtils : IDateTimeUtils + { + public DateTime UtcNow => DateTime.UtcNow; + } +} diff --git a/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs b/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs new file mode 100644 index 000000000..0654424dc --- /dev/null +++ b/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs @@ -0,0 +1,17 @@ +using System.Linq.Dynamic.Core.Validation; + +namespace System.Linq.Dynamic.Core.Util; + +internal static class TaskUtils +{ + public static void Run(Action action) + { + Check.NotNull(action); +#if NET35 || NET40 + System.Threading.ThreadPool.QueueUserWorkItem(_ => action.Invoke()); +#else + System.Threading.Tasks.Task.Run(action); +#endif + } +} + diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs new file mode 100644 index 000000000..31402e29f --- /dev/null +++ b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs @@ -0,0 +1,150 @@ +using Moq; +using System.Linq.Dynamic.Core.Util; +using System.Linq.Dynamic.Core.Util.Cache; +using System.Linq.Expressions; +using FluentAssertions; +using Xunit; + +namespace System.Linq.Dynamic.Core.Tests.Util.Cache; + +public class SlidingCacheTests +{ + private static readonly DateTime UtcNow = new(2024, 1, 1, 0, 0, 0); + + [Fact] + public void ThreadSafeSlidingCache_CacheOperations() + { + var dateTimeUtilsMock = new Mock(); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + + // Arrange + var cache = new SlidingCache( + TimeSpan.FromSeconds(1), + dateTimeProvider: dateTimeUtilsMock.Object); + + // Add + cache.AddOrUpdate(1, "one"); + cache.AddOrUpdate(2, "two"); + cache.AddOrUpdate(3, "three"); + + // Replace + cache.AddOrUpdate(1, "oneone"); + + cache.Count.Should().Be(3, $"Expected 3 items in the cache, only had {cache.Count}"); + + // Test retrieval + Assert.True(cache.TryGetValue(1, out var value1), "Expected to find the value, but did not"); + Assert.True(cache.TryGetValue(2, out var value2), "Expected to find the value, but did not"); + Assert.True(cache.TryGetValue(3, out var value3), "Expected to find the value, but did not"); + + // Test Removal + cache.Remove(1); + cache.Count.Should().Be(2, $"Expected 2 items in the cache, only had {cache.Count}"); + } + + [Fact] + public void ThreadSafeSlidingCache_TestExpire() + { + var dateTimeUtilsMock = new Mock(); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + + var cache = new SlidingCache(TimeSpan.FromMinutes(10), + dateTimeProvider: dateTimeUtilsMock.Object); + + // Act + cache.AddOrUpdate(1, "one"); + + var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); + + if (cache.TryGetValue(1, out var value)) + { + Assert.True(false, $"Expected to not find the value, but found {value}"); + } + } + + [Fact] + public void ThreadSafeSlidingCache_TestAutoExpire() + { + var dateTimeUtilsMock = new Mock(); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + + // Arrange + var cache = new SlidingCache( + TimeSpan.FromMinutes(10), + dateTimeProvider: dateTimeUtilsMock.Object); + + // Act + cache.AddOrUpdate(1, "one"); + + // Ensure one item is in the cache + cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); + + // move the time forward + var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); + + // Trigger the cleanup, asking for non-existing key + cache.TryGetValue(10, out var _); + + // Since the cache cleanup is triggered by a Task and not on the same thread, + // give it a moment for the cleanup to happen + System.Threading.Thread.Sleep(10); + + // Ensure one item is in the cache + cache.Count.Should().Be(0, $"Expected 0 items in the cache, only had {cache.Count}"); + } + + [Fact] + public void ThreadSafeSlidingCache_TestNull() + { + // Arrange + var cache = new SlidingCache(TimeSpan.FromMinutes(10)); + + // Expect an ArgumentNullException + var exception = Assert.Throws(() => { cache.AddOrUpdate(null, "one"); }); + } + + [Fact] + public void ThreadSafeSlidingCache_TestMinNumberBeforeTests() + { + var dateTimeUtilsMock = new Mock(); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + + // Arrange + var cache = new SlidingCache( + TimeSpan.FromMinutes(10), + minCacheItemsBeforeCleanup: 2, + dateTimeProvider: dateTimeUtilsMock.Object); + + // Act + cache.AddOrUpdate(1, "one"); + + // Ensure one item is in the cache + cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); + + // move the time forward + var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); + + // Trigger the cleanup, asking for non-existing key + cache.TryGetValue(10, out var _); + + // Since the cache cleanup is triggered by a Task and not on the same thread, + // give it a moment for the cleanup to happen + System.Threading.Thread.Sleep(10); + + // Ensure one item is in the cache + cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); + + // Act + cache.AddOrUpdate(2, "two"); + + // Since the cache cleanup is triggered by a Task and not on the same thread, + // give it a moment for the cleanup to happen + System.Threading.Thread.Sleep(10); + + // Ensure one item is in the cache + cache.Count.Should().Be(1, $"Expected 1 items in the cache, had {cache.Count}"); + } +} \ No newline at end of file From 225cc67bbdfede38b3374794557afd92dc43d4c5 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Mon, 22 Jan 2024 20:04:14 +0100 Subject: [PATCH 019/153] Refactoring on SlidingCache code --- .../Parser/ConstantExpressionHelper.cs | 11 +++++----- .../Util/Cache/CacheConfig.cs | 4 ++-- .../Util/Cache/SlidingCache.cs | 21 ++++++++++-------- .../Util/Cache/SlidingCacheConstants.cs | 5 ++++- .../Util/TaskUtils.cs | 3 +-- ...System.Linq.Dynamic.Core.Tests.Net8.csproj | 6 ----- .../Util/Cache/SlidingCacheTests.cs | 22 +++++++++---------- 7 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs index bae556f8e..d94b57bbe 100644 --- a/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs +++ b/src/System.Linq.Dynamic.Core/Parser/ConstantExpressionHelper.cs @@ -1,4 +1,5 @@ -using System.Linq.Dynamic.Core.Util.Cache; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Dynamic.Core.Util.Cache; using System.Linq.Dynamic.Core.Validation; using System.Linq.Expressions; @@ -12,13 +13,13 @@ internal class ConstantExpressionHelper public ConstantExpressionHelper(ParsingConfig config) { var parsingConfig = Check.NotNull(config); - var useConfig = parsingConfig.ConstantExpressionCacheConfig ?? new CacheConfig(); + var cacheConfig = parsingConfig.ConstantExpressionCacheConfig ?? new CacheConfig(); - _literals = new SlidingCache(useConfig); - _expressions = new SlidingCache(useConfig); + _literals = new SlidingCache(cacheConfig); + _expressions = new SlidingCache(cacheConfig); } - public bool TryGetText(Expression expression, out string? text) + public bool TryGetText(Expression expression, [NotNullWhen(true)] out string? text) { return _literals.TryGetValue(expression, out text); } diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs index d1a63a35c..66aeb74fc 100644 --- a/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs +++ b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs @@ -10,7 +10,7 @@ public class CacheConfig /// Items not accessed within this TTL will be expired, allowing garbage collection to reclaim the memory. /// Default is 10 minutes. /// - public TimeSpan TimeToLive { get; set; } = TimeSpan.FromMinutes(10); + public TimeSpan TimeToLive { get; set; } = SlidingCacheConstants.DefaultTimeToLive; /// /// Configures the minimum number of items required in the constant expression cache before triggering cleanup. @@ -23,5 +23,5 @@ public class CacheConfig /// Sets the frequency for running the cleanup process in the Constant Expression cache. /// By default, cleanup occurs every 10 minutes. /// - public TimeSpan CleanupFrequency { get; set; } = TimeSpan.FromMinutes(10); + public TimeSpan CleanupFrequency { get; set; } = SlidingCacheConstants.DefaultCleanupFrequency; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs index 5697bc34b..f12542938 100644 --- a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs +++ b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs @@ -45,16 +45,19 @@ public SlidingCache( /// /// Sliding Thread Safe Cache /// - /// - /// - public SlidingCache( - CacheConfig cashConfig, - IDateTimeUtils? dateTimeProvider = null) + /// The to use. + /// + /// Provides the Time for the Caching object. Default will be created if not supplied. Used + /// for Testing classes + /// + public SlidingCache(CacheConfig cacheConfig, IDateTimeUtils? dateTimeProvider = null) { + Check.NotNull(cacheConfig); + _cache = new ConcurrentDictionary>(); - TimeToLive = cashConfig.TimeToLive; - _minCacheItemsBeforeCleanup = cashConfig.MinItemsTrigger; - _cleanupFrequency = cashConfig.CleanupFrequency; + TimeToLive = cacheConfig.TimeToLive; + _minCacheItemsBeforeCleanup = cacheConfig.MinItemsTrigger; + _cleanupFrequency = cacheConfig.CleanupFrequency; _deleteExpiredCachedItemsDelegate = Cleanup; _dateTimeProvider = dateTimeProvider ?? new DateTimeUtils(); // To prevent a scan on first call, set the last Cleanup to the current Provider time @@ -151,4 +154,4 @@ private void Cleanup() } } } -} +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs index 3dd8ee8bd..9e6ae32ca 100644 --- a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs +++ b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCacheConstants.cs @@ -4,4 +4,7 @@ internal static class SlidingCacheConstants { // Default cleanup frequency public static readonly TimeSpan DefaultCleanupFrequency = TimeSpan.FromMinutes(10); -} + + // Default Time-To-Live + public static readonly TimeSpan DefaultTimeToLive = TimeSpan.FromMinutes(10); +} \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs b/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs index 0654424dc..d78aa3bc6 100644 --- a/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs +++ b/src/System.Linq.Dynamic.Core/Util/TaskUtils.cs @@ -13,5 +13,4 @@ public static void Run(Action action) System.Threading.Tasks.Task.Run(action); #endif } -} - +} \ No newline at end of file diff --git a/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj b/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj index f36383144..f1c3db358 100644 --- a/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj +++ b/test/System.Linq.Dynamic.Core.Tests.Net8/System.Linq.Dynamic.Core.Tests.Net8.csproj @@ -48,10 +48,4 @@ - - - - - - diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs index 31402e29f..1becd9e87 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs @@ -12,7 +12,7 @@ public class SlidingCacheTests private static readonly DateTime UtcNow = new(2024, 1, 1, 0, 0, 0); [Fact] - public void ThreadSafeSlidingCache_CacheOperations() + public void SlidingCache_CacheOperations() { var dateTimeUtilsMock = new Mock(); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); @@ -43,7 +43,7 @@ public void ThreadSafeSlidingCache_CacheOperations() } [Fact] - public void ThreadSafeSlidingCache_TestExpire() + public void SlidingCache_TestExpire() { var dateTimeUtilsMock = new Mock(); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); @@ -64,7 +64,7 @@ public void ThreadSafeSlidingCache_TestExpire() } [Fact] - public void ThreadSafeSlidingCache_TestAutoExpire() + public void SlidingCache_TestAutoExpire() { var dateTimeUtilsMock = new Mock(); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); @@ -85,28 +85,28 @@ public void ThreadSafeSlidingCache_TestAutoExpire() dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Trigger the cleanup, asking for non-existing key - cache.TryGetValue(10, out var _); + cache.TryGetValue(10, out _); // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - System.Threading.Thread.Sleep(10); + Threading.Thread.Sleep(10); // Ensure one item is in the cache cache.Count.Should().Be(0, $"Expected 0 items in the cache, only had {cache.Count}"); } [Fact] - public void ThreadSafeSlidingCache_TestNull() + public void SlidingCache_TestNull() { // Arrange var cache = new SlidingCache(TimeSpan.FromMinutes(10)); // Expect an ArgumentNullException - var exception = Assert.Throws(() => { cache.AddOrUpdate(null, "one"); }); + Assert.Throws(() => { cache.AddOrUpdate(null, "one"); }); } [Fact] - public void ThreadSafeSlidingCache_TestMinNumberBeforeTests() + public void SlidingCache_TestMinNumberBeforeTests() { var dateTimeUtilsMock = new Mock(); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); @@ -128,11 +128,11 @@ public void ThreadSafeSlidingCache_TestMinNumberBeforeTests() dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Trigger the cleanup, asking for non-existing key - cache.TryGetValue(10, out var _); + cache.TryGetValue(10, out _); // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - System.Threading.Thread.Sleep(10); + Threading.Thread.Sleep(10); // Ensure one item is in the cache cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); @@ -142,7 +142,7 @@ public void ThreadSafeSlidingCache_TestMinNumberBeforeTests() // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - System.Threading.Thread.Sleep(10); + Threading.Thread.Sleep(10); // Ensure one item is in the cache cache.Count.Should().Be(1, $"Expected 1 items in the cache, had {cache.Count}"); From db0824e5955b5763e843ed24521ab368d279ad44 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Thu, 1 Feb 2024 12:58:59 +0100 Subject: [PATCH 020/153] v1.3.9-preview-01 --- CHANGELOG.md | 4 ++++ Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3248e40..f47bc6310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v1.3.9-preview-01 (23 January 2024) +- [#761](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/761) - Add unit tests for Any [test] contributed by [StefH](https://github.com/StefH) +- [#765](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/765) - #764 - Introduce Sliding Cache to Constant Expression Helper [feature] contributed by [TWhidden](https://github.com/TWhidden) + # v1.3.8 (20 January 2024) - [#758](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/758) - Update function argument parsing for strings [bug] contributed by [StefH](https://github.com/StefH) - [#760](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/760) - Update function argument parsing for strings (part 2) [bug] contributed by [StefH](https://github.com/StefH) diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index 9d6fc41cd..df791f9a7 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.8 +SET version=v1.3.9-preview-01 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index 07e54a693..c88d94304 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 8 + 9-preview-01 \ No newline at end of file From 7e3b8edd3575c7adf9089d7801aa2df6dfff0bc2 Mon Sep 17 00:00:00 2001 From: Travis Whidden Date: Sat, 3 Feb 2024 06:12:10 -0800 Subject: [PATCH 021/153] Enhancements to SlidingCache<> for Improved Thread Safety and Performance (#770) * #769 - Enhancements to SlidingCache<> for Improved Thread Safety and Performance * #769 - Rename Config ReturnExpiredItems; Refactor Tests * #769 - SlidingCache Test Code-Comment Cleanup --------- Co-authored-by: Travis Whidden --- .../Util/Cache/CacheConfig.cs | 9 +++ .../Util/Cache/SlidingCache.cs | 78 +++++++++++++------ .../Util/Cache/SlidingCacheTests.cs | 34 +++++++- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs index 66aeb74fc..8bf46fdbf 100644 --- a/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs +++ b/src/System.Linq.Dynamic.Core/Util/Cache/CacheConfig.cs @@ -24,4 +24,13 @@ public class CacheConfig /// By default, cleanup occurs every 10 minutes. /// public TimeSpan CleanupFrequency { get; set; } = SlidingCacheConstants.DefaultCleanupFrequency; + + /// + /// Enables returning expired cache items in scenarios where cleanup, running on a separate thread, + /// has not yet removed them. This allows for the retrieval of an expired object without needing to + /// clear and recreate it if a request is made concurrently with cleanup. Particularly useful + /// when cached items are deterministic, ensuring consistent results even from expired entries. + /// Default true; + /// + public bool ReturnExpiredItems { get; set; } = true; } \ No newline at end of file diff --git a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs index f12542938..97b77051e 100644 --- a/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs +++ b/src/System.Linq.Dynamic.Core/Util/Cache/SlidingCache.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Linq.Dynamic.Core.Validation; +using System.Threading; namespace System.Linq.Dynamic.Core.Util.Cache; @@ -11,7 +12,9 @@ internal class SlidingCache where TKey : notnull where TValue : no private readonly IDateTimeUtils _dateTimeProvider; private readonly Action _deleteExpiredCachedItemsDelegate; private readonly long? _minCacheItemsBeforeCleanup; + private readonly bool _returnExpiredItems; private DateTime _lastCleanupTime; + private int _cleanupLocked = 0; /// /// Sliding Thread Safe Cache @@ -26,15 +29,19 @@ internal class SlidingCache where TKey : notnull where TValue : no /// Provides the Time for the Caching object. Default will be created if not supplied. Used /// for Testing classes /// + /// If a request for an item happens to be expired, but is still + /// in known, don't expire it and return it instead. public SlidingCache( TimeSpan timeToLive, TimeSpan? cleanupFrequency = null, long? minCacheItemsBeforeCleanup = null, - IDateTimeUtils? dateTimeProvider = null) + IDateTimeUtils? dateTimeProvider = null, + bool returnExpiredItems = false) { _cache = new ConcurrentDictionary>(); TimeToLive = timeToLive; _minCacheItemsBeforeCleanup = minCacheItemsBeforeCleanup; + _returnExpiredItems = returnExpiredItems; _cleanupFrequency = cleanupFrequency ?? SlidingCacheConstants.DefaultCleanupFrequency; _deleteExpiredCachedItemsDelegate = Cleanup; _dateTimeProvider = dateTimeProvider ?? new DateTimeUtils(); @@ -58,6 +65,7 @@ public SlidingCache(CacheConfig cacheConfig, IDateTimeUtils? dateTimeProvider = TimeToLive = cacheConfig.TimeToLive; _minCacheItemsBeforeCleanup = cacheConfig.MinItemsTrigger; _cleanupFrequency = cacheConfig.CleanupFrequency; + _returnExpiredItems = cacheConfig.ReturnExpiredItems; _deleteExpiredCachedItemsDelegate = Cleanup; _dateTimeProvider = dateTimeProvider ?? new DateTimeUtils(); // To prevent a scan on first call, set the last Cleanup to the current Provider time @@ -100,20 +108,29 @@ public bool TryGetValue(TKey key, [NotNullWhen(true)] out TValue? value) { Check.NotNull(key); - CleanupIfNeeded(); - - if (_cache.TryGetValue(key, out var valueAndExpiration)) + try { - if (_dateTimeProvider.UtcNow <= valueAndExpiration.ExpirationTime) + if (_cache.TryGetValue(key, out var valueAndExpiration)) { - value = valueAndExpiration.Value; - var newExpire = _dateTimeProvider.UtcNow.Add(TimeToLive); - _cache[key] = new CacheEntry(value, newExpire); - return true; + // Permit expired returns will return the object even if was expired + // this will prevent the need to re-create the object for the caller + if (_returnExpiredItems || _dateTimeProvider.UtcNow <= valueAndExpiration.ExpirationTime) + { + value = valueAndExpiration.Value; + var newExpire = _dateTimeProvider.UtcNow.Add(TimeToLive); + _cache[key] = new CacheEntry(value, newExpire); + return true; + } + + // Remove expired item + _cache.TryRemove(key, out _); } - - // Remove expired item - _cache.TryRemove(key, out _); + } + finally + { + // If permit expired returns are enabled, + // we want to ensure the cache has a chance to get the value + CleanupIfNeeded(); } value = default; @@ -131,26 +148,41 @@ public bool Remove(TKey key) private void CleanupIfNeeded() { - if (_dateTimeProvider.UtcNow - _lastCleanupTime > _cleanupFrequency - && (_minCacheItemsBeforeCleanup == null || - _cache.Count >= - _minCacheItemsBeforeCleanup) // Only cleanup if we have a minimum number of items in the cache. - ) + // Ensure this is only executing one at a time. + if (Interlocked.CompareExchange(ref _cleanupLocked, 1, 0) != 0) + { + return; + } + + try { - // Set here, so we don't have re-entry due to large collection enumeration. - _lastCleanupTime = _dateTimeProvider.UtcNow; + if (_dateTimeProvider.UtcNow - _lastCleanupTime > _cleanupFrequency + && (_minCacheItemsBeforeCleanup == null || + _cache.Count >= + _minCacheItemsBeforeCleanup) // Only cleanup if we have a minimum number of items in the cache. + ) + { + // Set here, so we don't have re-entry due to large collection enumeration. + _lastCleanupTime = _dateTimeProvider.UtcNow; - TaskUtils.Run(_deleteExpiredCachedItemsDelegate); + TaskUtils.Run(_deleteExpiredCachedItemsDelegate); + } + } + finally + { + // Release the lock + _cleanupLocked = 0; } } private void Cleanup() { - foreach (var key in _cache.Keys) + // Enumerate the key/value - safe per docs + foreach (var keyValue in _cache) { - if (_dateTimeProvider.UtcNow > _cache[key].ExpirationTime) + if (_dateTimeProvider.UtcNow > keyValue.Value.ExpirationTime) { - _cache.TryRemove(key, out _); + _cache.TryRemove(keyValue.Key, out _); } } } diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs index 1becd9e87..7d3da1242 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs @@ -15,6 +15,7 @@ public class SlidingCacheTests public void SlidingCache_CacheOperations() { var dateTimeUtilsMock = new Mock(); + // Configure Mock with SetupGet since SlidingCache can be non-deterministic; don't use SetupSequence dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); // Arrange @@ -46,27 +47,51 @@ public void SlidingCache_CacheOperations() public void SlidingCache_TestExpire() { var dateTimeUtilsMock = new Mock(); + // Configure Mock with SetupGet since SlidingCache can be non-deterministic; don't use SetupSequence dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + // Arrange var cache = new SlidingCache(TimeSpan.FromMinutes(10), dateTimeProvider: dateTimeUtilsMock.Object); // Act cache.AddOrUpdate(1, "one"); + // move the time forward + var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); + + // Ensure that the element has expired + cache.TryGetValue(1, out var value).Should().BeFalse($"Expected to not find the value, but found {value}"); + } + + [Fact] + public void SlidingCache_TestReturnExpiredItems() + { + var dateTimeUtilsMock = new Mock(); + // Configure Mock with SetupGet since SlidingCache can be non-deterministic; don't use SetupSequence + dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); + + // Arrange + var cache = new SlidingCache(TimeSpan.FromMinutes(10), + dateTimeProvider: dateTimeUtilsMock.Object, returnExpiredItems: true); + + // Act + cache.AddOrUpdate(1, "one"); + + // move the time forward var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); - if (cache.TryGetValue(1, out var value)) - { - Assert.True(false, $"Expected to not find the value, but found {value}"); - } + // Ensure the expired item is returned from the cache + cache.TryGetValue(1, out _).Should().BeTrue($"Expected to return expired item"); } [Fact] public void SlidingCache_TestAutoExpire() { var dateTimeUtilsMock = new Mock(); + // Configure Mock with SetupGet since SlidingCache can be non-deterministic; don't use SetupSequence dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); // Arrange @@ -109,6 +134,7 @@ public void SlidingCache_TestNull() public void SlidingCache_TestMinNumberBeforeTests() { var dateTimeUtilsMock = new Mock(); + // Configure Mock with SetupGet since SlidingCache can be non-deterministic; don't use SetupSequence dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(UtcNow); // Arrange From e6bc36e7d466f4664318f957bba77729ff19954c Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 3 Feb 2024 16:42:38 +0100 Subject: [PATCH 022/153] Fix SlidingCacheTests --- .../Util/Cache/SlidingCacheTests.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs index 7d3da1242..8e02d0efe 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs @@ -34,9 +34,9 @@ public void SlidingCache_CacheOperations() cache.Count.Should().Be(3, $"Expected 3 items in the cache, only had {cache.Count}"); // Test retrieval - Assert.True(cache.TryGetValue(1, out var value1), "Expected to find the value, but did not"); - Assert.True(cache.TryGetValue(2, out var value2), "Expected to find the value, but did not"); - Assert.True(cache.TryGetValue(3, out var value3), "Expected to find the value, but did not"); + Assert.True(cache.TryGetValue(1, out _), "Expected to find the value, but did not"); + Assert.True(cache.TryGetValue(2, out _), "Expected to find the value, but did not"); + Assert.True(cache.TryGetValue(3, out _), "Expected to find the value, but did not"); // Test Removal cache.Remove(1); @@ -58,7 +58,7 @@ public void SlidingCache_TestExpire() cache.AddOrUpdate(1, "one"); // move the time forward - var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + var newDateTime = UtcNow.AddMinutes(11); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Ensure that the element has expired @@ -79,12 +79,12 @@ public void SlidingCache_TestReturnExpiredItems() // Act cache.AddOrUpdate(1, "one"); - // move the time forward - var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + // move the time forward + var newDateTime = UtcNow.AddMinutes(11); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Ensure the expired item is returned from the cache - cache.TryGetValue(1, out _).Should().BeTrue($"Expected to return expired item"); + cache.TryGetValue(1, out _).Should().BeTrue("Expected to return expired item"); } [Fact] @@ -103,10 +103,10 @@ public void SlidingCache_TestAutoExpire() cache.AddOrUpdate(1, "one"); // Ensure one item is in the cache - cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); + cache.Count.Should().Be(1, $"Expected 1 items in the cache, had {cache.Count}"); // move the time forward - var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + var newDateTime = UtcNow.AddMinutes(11); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Trigger the cleanup, asking for non-existing key @@ -117,7 +117,7 @@ public void SlidingCache_TestAutoExpire() Threading.Thread.Sleep(10); // Ensure one item is in the cache - cache.Count.Should().Be(0, $"Expected 0 items in the cache, only had {cache.Count}"); + cache.Count.Should().Be(0, $"Expected 0 items in the cache, had {cache.Count}"); } [Fact] @@ -150,7 +150,7 @@ public void SlidingCache_TestMinNumberBeforeTests() cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); // move the time forward - var newDateTime = dateTimeUtilsMock.Object.UtcNow.AddMinutes(11); + var newDateTime = UtcNow.AddMinutes(11); dateTimeUtilsMock.SetupGet(d => d.UtcNow).Returns(newDateTime); // Trigger the cleanup, asking for non-existing key From a235842edd70fd8e4777904d28b386127dcc3af5 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 3 Feb 2024 16:52:19 +0100 Subject: [PATCH 023/153] v1.3.9-preview-02 --- CHANGELOG.md | 5 ++++- Generate-ReleaseNotes.bat | 2 +- version.xml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f47bc6310..66418d230 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ -# v1.3.9-preview-01 (23 January 2024) +# v1.3.9-preview-02 (03 February 2024) - [#761](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/761) - Add unit tests for Any [test] contributed by [StefH](https://github.com/StefH) - [#765](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/765) - #764 - Introduce Sliding Cache to Constant Expression Helper [feature] contributed by [TWhidden](https://github.com/TWhidden) +- [#770](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/770) - Enhancements to SlidingCache<> for Improved Thread Safety and Performance [feature] contributed by [TWhidden](https://github.com/TWhidden) +- [#764](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/764) - Possible Memory Leak with ConstantExpressionHelper [bug] +- [#769](https://github.com/zzzprojects/System.Linq.Dynamic.Core/issues/769) - Enhancements to SlidingCache<> for Improved Thread Safety and Performance [bug] # v1.3.8 (20 January 2024) - [#758](https://github.com/zzzprojects/System.Linq.Dynamic.Core/pull/758) - Update function argument parsing for strings [bug] contributed by [StefH](https://github.com/StefH) diff --git a/Generate-ReleaseNotes.bat b/Generate-ReleaseNotes.bat index df791f9a7..cbe181955 100644 --- a/Generate-ReleaseNotes.bat +++ b/Generate-ReleaseNotes.bat @@ -1,5 +1,5 @@ rem https://github.com/StefH/GitHubReleaseNotes -SET version=v1.3.9-preview-01 +SET version=v1.3.9-preview-02 GitHubReleaseNotes --output CHANGELOG.md --exclude-labels invalid question documentation wontfix --language en --version %version% --token %GH_TOKEN% diff --git a/version.xml b/version.xml index c88d94304..bc9834ded 100644 --- a/version.xml +++ b/version.xml @@ -1,5 +1,5 @@ - 9-preview-01 + 9-preview-02 \ No newline at end of file From 35c9904590673ea4be67f2c4dd01353ead2fb9e6 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 14 Feb 2024 17:27:16 +0100 Subject: [PATCH 024/153] Add Sleep(1000) in SlidingCacheTests --- .../Util/Cache/SlidingCacheTests.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs index 8e02d0efe..6b1caaae9 100644 --- a/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs +++ b/test/System.Linq.Dynamic.Core.Tests/Util/Cache/SlidingCacheTests.cs @@ -2,6 +2,7 @@ using System.Linq.Dynamic.Core.Util; using System.Linq.Dynamic.Core.Util.Cache; using System.Linq.Expressions; +using System.Threading; using FluentAssertions; using Xunit; @@ -114,7 +115,7 @@ public void SlidingCache_TestAutoExpire() // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - Threading.Thread.Sleep(10); + Sleep(); // Ensure one item is in the cache cache.Count.Should().Be(0, $"Expected 0 items in the cache, had {cache.Count}"); @@ -158,7 +159,7 @@ public void SlidingCache_TestMinNumberBeforeTests() // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - Threading.Thread.Sleep(10); + Sleep(); // Ensure one item is in the cache cache.Count.Should().Be(1, $"Expected 1 items in the cache, only had {cache.Count}"); @@ -168,9 +169,14 @@ public void SlidingCache_TestMinNumberBeforeTests() // Since the cache cleanup is triggered by a Task and not on the same thread, // give it a moment for the cleanup to happen - Threading.Thread.Sleep(10); + Sleep(); // Ensure one item is in the cache cache.Count.Should().Be(1, $"Expected 1 items in the cache, had {cache.Count}"); } + + private static void Sleep() + { + Thread.Sleep(1000); + } } \ No newline at end of file From c7286907c96e16dafc7b0116db6d82708f9cdba3 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Wed, 14 Feb 2024 20:50:36 +0100 Subject: [PATCH 025/153] Add RadzenDataGrid.BlazorApp example (#780) * Add RadzenDataGrid.BlazorApp example * use real db * nuget * --- --- System.Linq.Dynamic.Core.sln | 19 ++ .../Components/App.razor | 24 +++ .../Components/Layout/MainLayout.razor | 25 +++ .../Components/Layout/MainLayout.razor.css | 96 +++++++++++ .../Components/Layout/NavMenu.razor | 30 ++++ .../Components/Layout/NavMenu.razor.css | 105 ++++++++++++ .../Components/Pages/Counter.razor | 18 ++ .../Components/Pages/Error.razor | 36 ++++ .../Components/Pages/Home.razor | 162 ++++++++++++++++++ .../Components/Pages/Home.razor.css | 21 +++ .../Components/Pages/Weather.razor | 63 +++++++ .../Components/Routes.razor | 6 + .../Components/_Imports.razor | 12 ++ .../Entities/PriceListService.cs | 12 ++ .../Entities/PriceListServiceBase.cs | 10 ++ .../RadzenDataGrid.BlazorApp/MyDbContext.cs | 28 +++ .../RadzenDataGrid.BlazorApp/Program.cs | 43 +++++ .../Properties/launchSettings.json | 38 ++++ .../RadzenDataGrid.BlazorApp.csproj | 22 +++ .../appsettings.Development.json | 8 + .../RadzenDataGrid.BlazorApp/appsettings.json | 13 ++ .../RadzenDataGrid.BlazorApp/wwwroot/app.css | 51 ++++++ .../wwwroot/bootstrap/bootstrap.min.css | 7 + .../wwwroot/bootstrap/bootstrap.min.css.map | 1 + .../wwwroot/favicon.png | Bin 0 -> 1148 bytes 25 files changed, 850 insertions(+) create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/App.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor.css create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor.css create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Counter.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Error.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor.css create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Weather.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/Routes.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Components/_Imports.razor create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListService.cs create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListServiceBase.cs create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/MyDbContext.cs create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Program.cs create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/Properties/launchSettings.json create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/RadzenDataGrid.BlazorApp.csproj create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/appsettings.Development.json create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/appsettings.json create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/wwwroot/app.css create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css.map create mode 100644 src-blazor/RadzenDataGrid.BlazorApp/wwwroot/favicon.png diff --git a/System.Linq.Dynamic.Core.sln b/System.Linq.Dynamic.Core.sln index 0c684bf37..77dd9db8e 100644 --- a/System.Linq.Dynamic.Core.sln +++ b/System.Linq.Dynamic.Core.sln @@ -136,6 +136,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.EntityFrameworkCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Linq.Dynamic.Core.Tests.Net8", "test\System.Linq.Dynamic.Core.Tests.Net8\System.Linq.Dynamic.Core.Tests.Net8.csproj", "{ABB1BF71-8927-49BB-99F3-70BCB2CD161E}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RadzenDataGrid.BlazorApp", "src-blazor\RadzenDataGrid.BlazorApp\RadzenDataGrid.BlazorApp.csproj", "{51074A4C-15C2-4E72-81F2-2FC53903553B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -838,6 +840,22 @@ Global {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x64.Build.0 = Release|Any CPU {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x86.ActiveCfg = Release|Any CPU {ABB1BF71-8927-49BB-99F3-70BCB2CD161E}.Release|x86.Build.0 = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|ARM.ActiveCfg = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|ARM.Build.0 = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|x64.ActiveCfg = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|x64.Build.0 = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|x86.ActiveCfg = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Debug|x86.Build.0 = Debug|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|Any CPU.Build.0 = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|ARM.ActiveCfg = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|ARM.Build.0 = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|x64.ActiveCfg = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|x64.Build.0 = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|x86.ActiveCfg = Release|Any CPU + {51074A4C-15C2-4E72-81F2-2FC53903553B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -886,6 +904,7 @@ Global {CC63ECEB-18C1-462B-BAFC-7F146A7C2740} = {8463ED7E-69FB-49AE-85CF-0791AFD98E38} {9000129D-322D-4FE6-9C47-75464577C374} = {DBD7D9B6-FCC7-4650-91AF-E6457573A68F} {ABB1BF71-8927-49BB-99F3-70BCB2CD161E} = {8463ED7E-69FB-49AE-85CF-0791AFD98E38} + {51074A4C-15C2-4E72-81F2-2FC53903553B} = {122BC4FA-7563-4E35-9D17-077F16F1629F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {94C56722-194E-4B8B-BC23-B3F754E89A20} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/App.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/App.razor new file mode 100644 index 000000000..e299d92f7 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/App.razor @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor new file mode 100644 index 000000000..f001c98a2 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor @@ -0,0 +1,25 @@ +@inherits LayoutComponentBase + + + +
+ + +
+ + +
+ @Body +
+
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor.css b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor.css new file mode 100644 index 000000000..038baf178 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/MainLayout.razor.css @@ -0,0 +1,96 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor new file mode 100644 index 000000000..ffda8b26e --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor @@ -0,0 +1,30 @@ + + + + + + diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor.css b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor.css new file mode 100644 index 000000000..4e15395e0 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Layout/NavMenu.razor.css @@ -0,0 +1,105 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: #d7d7d7; + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep .nav-link:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Counter.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Counter.razor new file mode 100644 index 000000000..ef23cb316 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Counter.razor @@ -0,0 +1,18 @@ +@page "/counter" + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Error.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Error.razor new file mode 100644 index 000000000..576cc2d2f --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor new file mode 100644 index 000000000..5fec14109 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor @@ -0,0 +1,162 @@ +@page "/" +@using Microsoft.EntityFrameworkCore +@using Radzen +@using System.Linq.Dynamic.Core; +@using BlazorApp1.Entities +Home + +
+ Second step -> Load data from Db with OrderBy(x=>x.ServiceBase.Code) +
+ +@if (DataFromDb != null) +{ + + + + + + + + + + + @foreach (var item in DataFromDb) + { + + + + + + + } + +
IdIndexCodeName
@item.Id@item.ServiceBase.Index@item.ServiceBase.Code@item.ServiceBase.ServiceName
+} + +
+
+ + + + + + + + + + + + + +@if (ErrorMessages.Count > 0) +{ + @foreach (var item in ErrorMessages) + { +
@item
+ } +} + + +@code { + [Inject] + MyDbContext MyDbContext { get; set; } + + protected bool isLoading; + protected int count; + protected IEnumerable? Entities { get; set; } + protected IEnumerable? DataFromDb { get; set; } + protected List ErrorMessages { get; set; } = new(); + + protected override async Task OnInitializedAsync() + { + if (!MyDbContext.PriceListServices.Any(pls => pls.ServiceBase.Code != null && pls.ServiceBase.Code.Contains("stef"))) + { + var data = new List + { + new() { ServiceBase = new() { Index = 10, Code = "xb-001-stef", ServiceName = "Message" } }, + new() { ServiceBase = new() { Index = 11, Code = "xa-002-stef", ServiceName = "Info" } }, + new() { ServiceBase = new() { Index = 12, Code = "xw-003-stef", ServiceName = "Word" } }, + new() { ServiceBase = new() { Index = 13, Code = "xf-004-stef", ServiceName = "Hi" } }, + new() { ServiceBase = new() { Index = 14, Code = "xz-005-stef", ServiceName = "Zoom" } } + }; + MyDbContext.PriceListServices.AddRange(data); + await MyDbContext.SaveChangesAsync(); + } + } + + protected async Task OnLoadAllDataFromDb() + { + try + { + DataFromDb = await MyDbContext.PriceListServices.OrderBy(x => x.ServiceBase.Code).ToListAsync(); + } + catch (Exception ex) + { + ErrorMessages.Add(DateTime.Now + "? Exception: " + ex.Message + (ex.InnerException == null ? "" : ", InnerException: " + ex.InnerException.Message)); + } + } + + protected async Task OnLoadDataAsync(LoadDataArgs args) + { + isLoading = true; + + try + { + var query = MyDbContext.PriceListServices.AsQueryable(); + + if (!string.IsNullOrEmpty(args.Filter)) + { + query = query.Where(args.Filter); + } + + if (!string.IsNullOrEmpty(args.OrderBy)) + { + query = query.OrderBy(args.OrderBy); + } + + count = query.Count(); + + if (args.Skip.HasValue) + { + query = query.Skip(args.Skip.Value); + } + + if (args.Top.HasValue) + { + query = query.Take(args.Top.Value); + } + + Entities = await query.ToListAsync(); + } + catch (Exception ex) + { + ErrorMessages.Add(DateTime.Now + ", Exception: " + ex.Message + (ex.InnerException == null ? "" : ", InnerException: " + ex.InnerException.Message)); + } + finally + { + isLoading = false; + } + } +} \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor.css b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor.css new file mode 100644 index 000000000..4644a70b7 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Home.razor.css @@ -0,0 +1,21 @@ + +table { + border: 1px solid grey; + width: 40%; +} + +th { + border: 1px solid grey; +} + +td { + border: 1px solid grey; +} + +thead { + background-color: lightskyblue; +} + + tfoot { + background-color: lightskyblue; +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Weather.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Weather.razor new file mode 100644 index 000000000..8eca4cc44 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Pages/Weather.razor @@ -0,0 +1,63 @@ +@page "/weather" + +Weather + +

Weather

+ +

This component demonstrates showing data.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate a loading indicator + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); + } + + private class WeatherForecast + { + public DateOnly Date { get; set; } + public int TemperatureC { get; set; } + public string? Summary { get; set; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/Routes.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/Routes.razor new file mode 100644 index 000000000..f756e19df --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Components/_Imports.razor b/src-blazor/RadzenDataGrid.BlazorApp/Components/_Imports.razor new file mode 100644 index 000000000..c225865d1 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Components/_Imports.razor @@ -0,0 +1,12 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using BlazorApp1 +@using BlazorApp1.Components + +@using Radzen.Blazor \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListService.cs b/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListService.cs new file mode 100644 index 000000000..6cc660ea0 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListService.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace BlazorApp1.Entities; + +public class PriceListService +{ + [Key] + public int Id { get; set; } + + // [Required] + public PriceListServiceBase ServiceBase { get; set; } = new(); +} \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListServiceBase.cs b/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListServiceBase.cs new file mode 100644 index 000000000..ac00c6606 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Entities/PriceListServiceBase.cs @@ -0,0 +1,10 @@ +namespace BlazorApp1.Entities; + +public class PriceListServiceBase +{ + public int Index { get; set; } + + public string? Code { get; set; } + + public string? ServiceName { get; set; } +} \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/MyDbContext.cs b/src-blazor/RadzenDataGrid.BlazorApp/MyDbContext.cs new file mode 100644 index 000000000..4e3a31486 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/MyDbContext.cs @@ -0,0 +1,28 @@ +using BlazorApp1.Entities; +using Microsoft.EntityFrameworkCore; + +namespace BlazorApp1; + +public class MyDbContext : DbContext +{ + public MyDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity(entity => + { + entity.OwnsOne(e => e.ServiceBase, sb => + { + sb.Property(p => p.Index); + sb.Property(p => p.Code); + sb.Property(p => p.ServiceName); + }); + }); + } + + public DbSet PriceListServices { get; set; } = null!; +} \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Program.cs b/src-blazor/RadzenDataGrid.BlazorApp/Program.cs new file mode 100644 index 000000000..437fb9921 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Program.cs @@ -0,0 +1,43 @@ +using BlazorApp1.Components; +using Microsoft.EntityFrameworkCore; +using Radzen; +using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database; + +namespace BlazorApp1; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + builder.Services + .AddRazorComponents() + .AddInteractiveServerComponents(); + builder.Services.AddRadzenComponents(); + + builder.Services.AddDbContextFactory(opt => opt.UseInMemoryDatabase("RadzenDataGrid.BlazorApp")); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (!app.Environment.IsDevelopment()) + { + app.UseExceptionHandler("/Error"); + + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + + app.UseStaticFiles(); + app.UseAntiforgery(); + + app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + + app.Run(); + } +} \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/Properties/launchSettings.json b/src-blazor/RadzenDataGrid.BlazorApp/Properties/launchSettings.json new file mode 100644 index 000000000..e3fd96591 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/Properties/launchSettings.json @@ -0,0 +1,38 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:7163", + "sslPort": 44336 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7080;http://localhost:5088", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src-blazor/RadzenDataGrid.BlazorApp/RadzenDataGrid.BlazorApp.csproj b/src-blazor/RadzenDataGrid.BlazorApp/RadzenDataGrid.BlazorApp.csproj new file mode 100644 index 000000000..9878555a9 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/RadzenDataGrid.BlazorApp.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + BlazorApp1 + BlazorApp1 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/appsettings.Development.json b/src-blazor/RadzenDataGrid.BlazorApp/appsettings.Development.json new file mode 100644 index 000000000..0c208ae91 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/appsettings.json b/src-blazor/RadzenDataGrid.BlazorApp/appsettings.json new file mode 100644 index 000000000..cf311a6af --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/appsettings.json @@ -0,0 +1,13 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=RadzenDataGridBlazorApp;Trusted_Connection=True;" + }, + + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/app.css b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/app.css new file mode 100644 index 000000000..2bd9b7896 --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/app.css @@ -0,0 +1,51 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css new file mode 100644 index 000000000..02ae65b5f --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css.map b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css.map new file mode 100644 index 000000000..afcd9e33e --- /dev/null +++ b/src-blazor/RadzenDataGrid.BlazorApp/wwwroot/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-`