Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ internal async Task<List<ValidationIssue>> ValidateFormData(
continue;
}
var context = new ComponentContext(
evaluatorState,
component: null,
rowIndices: DataModel.GetRowIndices(resolvedField.Field),
dataElementIdentifier: resolvedField.DataElementIdentifier
Expand Down
99 changes: 99 additions & 0 deletions src/Altinn.App.Core/Helpers/NaturalStringComparerPolyfill.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#if !NET10_0_OR_GREATER
using System.Globalization;
Comment thread
ivarne marked this conversation as resolved.

namespace Altinn.App.Core.Helpers;

/// <summary>
/// Provides a custom string comparer that performs natural sorting, comparing numeric substrings as numbers rather than strings.
/// </summary>
/// <remarks>
/// This comparer is used to ensure that strings containing numeric values are ordered logically (e.g., "item2" &lt; "item10").
/// </remarks>
/// <example>
/// This class is marked as obsolete and is planned for removal when upgrading to .NET 10, as it will include built-in support for natural string comparison.
/// </example>
// [Obsolete("This utility can be removed in .NET 10")]
internal sealed class NaturalStringComparerPolyfill : IComparer<string>
{
private static readonly CompareInfo _compareInfo = CultureInfo.InvariantCulture.CompareInfo;

internal static readonly NaturalStringComparerPolyfill Instance = new();

/// <inheritdoc />
public int Compare(string? x, string? y)
{
if (ReferenceEquals(x, y))
return 0;
if (x is null)
return -1;
if (y is null)
return 1;

int i = 0,
j = 0;
while (i < x.Length && j < y.Length)
{
char cx = x[i];
char cy = y[j];

bool dx = IsAsciiDigit(cx);
bool dy = IsAsciiDigit(cy);

if (dx && dy)
{
// Read full digit runs
int sx = i;
while (i < x.Length && IsAsciiDigit(x[i]))
i++;
int sy = j;
while (j < y.Length && IsAsciiDigit(y[j]))
j++;

// Skip leading zeros
int zx = sx;
while (zx < i && x[zx] == '0')
zx++;
int zy = sy;
while (zy < j && y[zy] == '0')
zy++;

int lenX = i - zx;
int lenY = j - zy;

// Compare by numeric length (ignoring leading zeros)
if (lenX != lenY)
return lenX.CompareTo(lenY);

// Same length: compare digit-by-digit
for (int k = 0; k < lenX; k++)
{
int cmp = x[zx + k].CompareTo(y[zy + k]);
if (cmp != 0)
return cmp;
}

// Perfect numeric tie: shorter with more leading zeros comes first
int leadingZerosCmp = (i - sx).CompareTo(j - sy);
if (leadingZerosCmp != 0)
return leadingZerosCmp;

// Otherwise continue
}
Comment thread Dismissed
else
{
// ordnal for non digit chars
int cmp = _compareInfo.Compare(x, i, 1, y, j, 1, CompareOptions.Ordinal);
if (cmp != 0)
return cmp;
i++;
j++;
}
}

// Shorter string first if all equal so far
return (x.Length - i).CompareTo(y.Length - j);
}

private static bool IsAsciiDigit(char c) => c >= '0' && c <= '9';
}
#endif
15 changes: 7 additions & 8 deletions src/Altinn.App.Core/Implementation/AppResourcesSI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,14 @@ private LayoutSetComponent LoadLayout(LayoutSet layoutSet, List<DataType> dataTy
string folder = Path.Join(_settings.AppBasePath, _settings.UiFolder, layoutSet.Id, "layouts");
foreach (var page in order)
{
var pageBytes = File.ReadAllBytes(Path.Join(folder, page + ".json"));
// Set the PageName using AsyncLocal before deserializing.
PageComponentConverter.SetAsyncLocalPageName(layoutSet.Id, page);
pages.Add(
System.Text.Json.JsonSerializer.Deserialize<PageComponent>(
pageBytes.RemoveBom(),
_jsonSerializerOptions
) ?? throw new InvalidDataException(page + ".json is \"null\"")
var pagePath = Path.Join(folder, page + ".json");
PathHelper.EnsureLegalPath(folder, pagePath);
var pageBytes = File.ReadAllBytes(pagePath);
using var document = JsonDocument.Parse(
pageBytes,
new JsonDocumentOptions() { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }
);
pages.Add(new PageComponent(document.RootElement, page, layoutSet.Id));
}

// First look at the specified data type, but
Expand Down
12 changes: 6 additions & 6 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Altinn.App.Core.Models;
using Altinn.App.Core.Models.Expressions;
using Altinn.App.Core.Models.Layout;
using Altinn.App.Core.Models.Layout.Components;

namespace Altinn.App.Core.Internal.Expressions;

Expand All @@ -14,7 +13,7 @@ namespace Altinn.App.Core.Internal.Expressions;
public static class ExpressionEvaluator
{
/// <summary>
/// Shortcut for evaluating a boolean expression on a given property on a <see cref="BaseComponent" />
/// Shortcut for evaluating a boolean expression on a given property on a <see cref="Models.Layout.Components.Base.BaseComponent" />
/// </summary>
public static async Task<bool> EvaluateBooleanExpression(
LayoutEvaluatorState state,
Expand All @@ -40,6 +39,7 @@ bool defaultReturn
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => defaultReturn,
JsonValueKind.Undefined => defaultReturn,
_ => throw new ExpressionEvaluatorTypeErrorException(
$"Return was not boolean. Was {result} of type {result.ValueKind}"
),
Expand Down Expand Up @@ -72,7 +72,7 @@ bool defaultReturn
/// <summary>
/// private implementation in order to change the types of positional arguments without breaking change.
/// </summary>
private static async Task<ExpressionValue> EvaluateExpression_internal(
internal static async Task<ExpressionValue> EvaluateExpression_internal(
LayoutEvaluatorState state,
Expression expr,
ComponentContext context,
Expand Down Expand Up @@ -259,16 +259,16 @@ LayoutEvaluatorState state
throw new ArgumentException($"Unable to find component with identifier {componentId}{rowIndexInfo}");
}

if (targetContext.Component is GroupComponent)
if (targetContext.HasChildContexts)
{
throw new NotImplementedException("Component lookup for components in groups not implemented");
throw new NotImplementedException("Component lookup for components that are groups is not implemented");
}

if (targetContext.Component?.DataModelBindings.TryGetValue("simpleBinding", out var binding) != true)
{
throw new ArgumentException("component lookup requires the target component to have a simpleBinding");
}
if (await targetContext.IsHidden(state))
if (await targetContext.IsHidden())
{
return ExpressionValue.Null;
}
Expand Down
22 changes: 16 additions & 6 deletions src/Altinn.App.Core/Internal/Expressions/ExpressionValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,35 @@ namespace Altinn.App.Core.Internal.Expressions;
// private readonly ExpressionValue[]? _arrayValue = null;

/// <summary>
/// Constructor for NULL value
/// Constructor for NULL value (structs require a public parameterless constructor)
/// </summary>
public ExpressionValue()
: this(JsonValueKind.Null) { }

private ExpressionValue(JsonValueKind valueKind)
{
_valueKind = JsonValueKind.Null;
_valueKind = valueKind;
}

/// <summary>
/// Convenient accessor for NULL value
/// </summary>
public static ExpressionValue Null => new();
public static ExpressionValue Null => new(JsonValueKind.Null);

/// <summary>
/// Convenient accessor for true value
/// </summary>
public static ExpressionValue True => new(true);
public static ExpressionValue True => new(JsonValueKind.True);

/// <summary>
/// Convenient accessor for false value
/// </summary>
public static ExpressionValue False => new(false);
public static ExpressionValue False => new(JsonValueKind.False);

/// <summary>
/// Convenient accessor for undefined value
/// </summary>
public static ExpressionValue Undefined => new(JsonValueKind.Undefined);

Comment thread
coderabbitai[bot] marked this conversation as resolved.
private ExpressionValue(bool? value)
{
Expand Down Expand Up @@ -261,7 +269,7 @@ public override string ToString() =>
JsonValueKind.Number => Number.ToString(CultureInfo.InvariantCulture),
// JsonValueKind.Object => JsonSerializer.Serialize(Object),
// JsonValueKind.Array => JsonSerializer.Serialize(Array),
_ => throw new InvalidOperationException("Invalid value kind"),
_ => throw new InvalidOperationException($"Invalid value kind {ValueKind}"),
};

/// <summary>
Expand All @@ -275,6 +283,7 @@ public override string ToString() =>
ValueKind switch
{
JsonValueKind.Null => null,
JsonValueKind.Undefined => null,
JsonValueKind.True => "true",
JsonValueKind.False => "false",
JsonValueKind.String => String switch
Expand Down Expand Up @@ -396,6 +405,7 @@ public override void Write(Utf8JsonWriter writer, ExpressionValue value, JsonSer
switch (value.ValueKind)
{
case JsonValueKind.Null:
case JsonValueKind.Undefined:
writer.WriteNullValue();
break;
case JsonValueKind.True:
Expand Down
Loading
Loading