From b75da90f179155f9ae9dfe7f0ab26f298d106cf6 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Wed, 29 Apr 2026 14:57:50 +0300 Subject: [PATCH 01/13] Fix known issues with validation false positives when using known alias for dimension types --- .../ContentValidationTests.cs | 21 +++++++++++++++++++ .../Fixtures/ContentValidationFixtures.cs | 9 ++++++++ .../ModelBuilders/ValueParserUtilities.cs | 4 +++- Px.Utils/PxFile/PxFileConfiguration.cs | 6 +++++- ...ntentValidator.ValidationEntryFunctions.cs | 16 +++++++++++--- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index c3b32180..cb596c6b 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -536,6 +536,27 @@ public void ValidateValueTypesCalledWithStructuredEntryArrayWithWrongValuesRetur } } + [TestMethod] + public void ValidateValueTypesCalledWithStructuredEntryArrayWithKnownDimensionTypeAliasesReturnsWithWarnings() + { + // Arrange + ValidationStructuredEntry[] entries = ContentValidationFixtures.STRUCTURED_ENTRY_ARRAY_WITH_KNOWN_DIMENSIONTYPE_ALIASES; + ContentValidator validator = new(filename, encoding, entries); + // Act + foreach (ValidationStructuredEntry entry in entries) + { + ValidationFeedback? result = ContentValidator.ValidateValueContents( + entry, + validator + ); + // Assert + Assert.IsNotNull(result); + Assert.HasCount(1, result); + Assert.AreEqual(ValidationFeedbackRule.InvalidValueFound, result.First().Key.Rule); + Assert.AreEqual(ValidationFeedbackLevel.Warning, result.First().Key.Level); + } + } + [TestMethod] public void ValidateValueAmountsCalledWithUnmatchingAmountOfElementsReturnsWithError() { diff --git a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs index 67d36528..ae4ee466 100644 --- a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs +++ b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs @@ -429,6 +429,9 @@ internal static class ContentValidationFixtures 17, Utils.Validation.ValueType.StringValue); + private static readonly ValidationStructuredEntryKey dimensionTypeEntryKey = + new ("VARIABLE-TYPE", "fi", "foo"); + internal static ValidationStructuredEntry[] MINIMAL_STRUCTURED_ENTRY_ARRAY => [ charsetEntry, @@ -752,6 +755,12 @@ internal static class ContentValidationFixtures Utils.Validation.ValueType.StringValue), ]; + internal static ValidationStructuredEntry[] STRUCTURED_ENTRY_ARRAY_WITH_KNOWN_DIMENSIONTYPE_ALIASES => + [ + new ValidationStructuredEntry(filename, dimensionTypeEntryKey, "Contents", 0, [], 0, Utils.Validation.ValueType.StringValue), + new ValidationStructuredEntry(filename, dimensionTypeEntryKey, "Region", 1, [], 0, Utils.Validation.ValueType.StringValue), + ]; + internal static ValidationStructuredEntry StructuredEntryWithUnmatchingAmountOfElements => new(filename, codesBarEntry.Key, diff --git a/Px.Utils/ModelBuilders/ValueParserUtilities.cs b/Px.Utils/ModelBuilders/ValueParserUtilities.cs index 33cbe847..ced54567 100644 --- a/Px.Utils/ModelBuilders/ValueParserUtilities.cs +++ b/Px.Utils/ModelBuilders/ValueParserUtilities.cs @@ -1,4 +1,4 @@ -using Px.Utils.Models.Metadata.Enums; +using Px.Utils.Models.Metadata.Enums; using Px.Utils.Models.Metadata.ExtensionMethods; using Px.Utils.PxFile; @@ -113,10 +113,12 @@ public static DimensionType StringToDimensionType(string input, PxFileConfigurat Dictionary map = new() { {conf.Tokens.VariableTypes.Content, DimensionType.Content}, + {conf.Tokens.VariableTypes.Contents, DimensionType.Content}, // Known alias for content dimension type {conf.Tokens.VariableTypes.Time, DimensionType.Time}, {conf.Tokens.VariableTypes.Ordinal, DimensionType.Ordinal}, {conf.Tokens.VariableTypes.Nominal, DimensionType.Nominal}, {conf.Tokens.VariableTypes.Geographical, DimensionType.Geographical}, + {conf.Tokens.VariableTypes.Region, DimensionType.Geographical}, // Known alias for geographical dimension type {conf.Tokens.VariableTypes.Other, DimensionType.Other}, {conf.Tokens.VariableTypes.Unknown, DimensionType.Unknown} }; diff --git a/Px.Utils/PxFile/PxFileConfiguration.cs b/Px.Utils/PxFile/PxFileConfiguration.cs index f1cbf897..ce244a54 100644 --- a/Px.Utils/PxFile/PxFileConfiguration.cs +++ b/Px.Utils/PxFile/PxFileConfiguration.cs @@ -1,4 +1,4 @@ -using Px.Utils.Models.Metadata.Enums; +using Px.Utils.Models.Metadata.Enums; namespace Px.Utils.PxFile { @@ -110,19 +110,23 @@ private TimeValue() { } public class VariableTypeTokens { private const string CONTENT = "Content"; + private const string CONTENTS = "Contents"; private const string TIME = "Time"; private const string ORDINAL = "Ordinal"; private const string NOMINAL = "Nominal"; private const string GEOGRAPHICAL = "Geographical"; + private const string REGION = "Region"; private const string OTHER = "Other"; private const string UNKNOWN = "Unknown"; private const string CLASSIFICATORY = "Classificatory"; public string Content { get; set; } = CONTENT; + public string Contents { get; set; } = CONTENTS; public string Time { get; set; } = TIME; public string Ordinal { get; set; } = ORDINAL; public string Nominal { get; set; } = NOMINAL; public string Geographical { get; set; } = GEOGRAPHICAL; + public string Region { get; set; } = REGION; public string Other { get; set; } = OTHER; public string Unknown { get; set; } = UNKNOWN; public string Classificatory { get; set; } = CLASSIFICATORY; diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index 0de2425a..8f71281d 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -323,7 +323,7 @@ public sealed partial class ContentValidator string[] allowedCharsets = ["ANSI", "Unicode"]; string[] dimensionTypes = [ - validator.Conf.Tokens.VariableTypes.Content, + validator.Conf.Tokens.VariableTypes.Content, validator.Conf.Tokens.VariableTypes.Time, validator.Conf.Tokens.VariableTypes.Geographical, validator.Conf.Tokens.VariableTypes.Ordinal, @@ -331,20 +331,30 @@ public sealed partial class ContentValidator validator.Conf.Tokens.VariableTypes.Other, validator.Conf.Tokens.VariableTypes.Unknown, validator.Conf.Tokens.VariableTypes.Classificatory - ]; + ]; + + string[] knownDimensionTypeAliases = [ + validator.Conf.Tokens.VariableTypes.Contents, + validator.Conf.Tokens.VariableTypes.Region, + ]; string value = SyntaxValidationUtilityMethods.CleanString(entry.Value, validator.Conf); if ((entry.Key.Keyword == validator.Conf.Tokens.KeyWords.Charset && !allowedCharsets.Contains(value)) || (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.CodePage && !value.Equals(validator._encoding.BodyName, StringComparison.OrdinalIgnoreCase)) || (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && !dimensionTypes.Contains(value))) { + // If the value is included in known aliases, set level to warning instead of error + ValidationFeedbackLevel level = entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && knownDimensionTypeAliases.Contains(value) ? + ValidationFeedbackLevel.Warning : + ValidationFeedbackLevel.Error; + KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( entry.KeyStartLineIndex, entry.ValueStartIndex, entry.LineChangeIndexes); KeyValuePair feedback = new( - new(ValidationFeedbackLevel.Error, + new(level, ValidationFeedbackRule.InvalidValueFound), new(validator._filename, feedbackIndexes.Key, From 31ab9a5e86e1d5439c4d35fd4138782a0a6b2972 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Thu, 30 Apr 2026 08:17:50 +0300 Subject: [PATCH 02/13] Rename test --- .../Validation/ContentValidationTests/ContentValidationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index cb596c6b..e647a6fc 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -537,7 +537,7 @@ public void ValidateValueTypesCalledWithStructuredEntryArrayWithWrongValuesRetur } [TestMethod] - public void ValidateValueTypesCalledWithStructuredEntryArrayWithKnownDimensionTypeAliasesReturnsWithWarnings() + public void ValidateValueContentsCalledWithStructuredEntryArrayWithKnownDimensionTypeAliasesReturnsWithWarnings() { // Arrange ValidationStructuredEntry[] entries = ContentValidationFixtures.STRUCTURED_ENTRY_ARRAY_WITH_KNOWN_DIMENSIONTYPE_ALIASES; From c2d5794ce08e89c3b6071ea40d04db1f209d9940 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Thu, 30 Apr 2026 09:12:46 +0300 Subject: [PATCH 03/13] Support for custom dimension type aliases --- .../StringToDimensionTypeTests.cs | 26 +++++++- .../ContentValidationTests.cs | 29 +++++++++ .../ModelBuilders/ValueParserUtilities.cs | 45 +++++++++---- Px.Utils/PxFile/PxFileConfiguration.cs | 26 ++++---- ...ntentValidator.ValidationEntryFunctions.cs | 64 +++++++++++++------ docs/architecture.models.md | 2 + docs/architecture.validation.md | 2 + 7 files changed, 147 insertions(+), 47 deletions(-) diff --git a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs index c059b6d9..e7d4db91 100644 --- a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs +++ b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs @@ -1,7 +1,8 @@ -using Px.Utils.ModelBuilders; +using Px.Utils.ModelBuilders; using Px.Utils.Models.Metadata.Enums; +using Px.Utils.PxFile; -namespace ModelBuilderTests.ValueParserUtilitiesTests +namespace Px.Utils.UnitTests.ModelBuilderTests.ValueParserUtilitiesTests { [TestClass] public class StringToDimensionTypeTests @@ -24,6 +25,27 @@ public void ContentTest() Assert.AreEqual(expected, actual); } + [TestMethod] + public void ContentAliasTest() + { + string input = "Contents"; + DimensionType expected = DimensionType.Content; + DimensionType actual = ValueParserUtilities.StringToDimensionType(input); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void CustomAliasTest() + { + string input = "Ranking"; + PxFileConfiguration conf = PxFileConfiguration.Default; + conf.Tokens.VariableTypes.Ordinal = ["Ordinal", "Ranking"]; + + DimensionType expected = DimensionType.Ordinal; + DimensionType actual = ValueParserUtilities.StringToDimensionType(input, conf); + Assert.AreEqual(expected, actual); + } + [TestMethod] public void DefaultValueTest() { diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index e647a6fc..4054f81f 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -2,6 +2,7 @@ using Px.Utils.Validation; using Px.Utils.Validation.ContentValidation; using Px.Utils.Validation.SyntaxValidation; +using Px.Utils.PxFile; using System.Text; using System.Reflection; @@ -557,6 +558,34 @@ public void ValidateValueContentsCalledWithStructuredEntryArrayWithKnownDimensio } } + [TestMethod] + public void ValidateValueContentsCalledWithConfiguredDimensionTypeAliasReturnsWithWarning() + { + // Arrange + PxFileConfiguration conf = PxFileConfiguration.Default; + conf.Tokens.VariableTypes.Ordinal = ["Ordinal", "Ranking"]; + + ValidationStructuredEntry entry = new( + filename, + new ValidationStructuredEntryKey("VARIABLE-TYPE", "fi", "foo"), + "Ranking", + 0, + [], + 0, + Px.Utils.Validation.ValueType.StringValue); + + ContentValidator validator = new(filename, encoding, [entry], conf: conf); + + // Act + ValidationFeedback? result = ContentValidator.ValidateValueContents(entry, validator); + + // Assert + Assert.IsNotNull(result); + Assert.HasCount(1, result); + Assert.AreEqual(ValidationFeedbackRule.InvalidValueFound, result.First().Key.Rule); + Assert.AreEqual(ValidationFeedbackLevel.Warning, result.First().Key.Level); + } + [TestMethod] public void ValidateValueAmountsCalledWithUnmatchingAmountOfElementsReturnsWithError() { diff --git a/Px.Utils/ModelBuilders/ValueParserUtilities.cs b/Px.Utils/ModelBuilders/ValueParserUtilities.cs index ced54567..1a9792a5 100644 --- a/Px.Utils/ModelBuilders/ValueParserUtilities.cs +++ b/Px.Utils/ModelBuilders/ValueParserUtilities.cs @@ -102,6 +102,7 @@ public static string GetTimeValValueRangeString(string input, PxFileConfiguratio /// /// Parses a string into a enumeration value. /// This method maps the input string to a enumeration value based on the provided or default PxFileConfiguration configuration. + /// The first configured token for a dimension type is treated as the primary value and any additional configured tokens are treated as aliases. /// If the input string does not map to a known , the method returns . /// /// The string to parse into a enumeration value. @@ -110,22 +111,40 @@ public static string GetTimeValValueRangeString(string input, PxFileConfiguratio public static DimensionType StringToDimensionType(string input, PxFileConfiguration? conf = null) { conf ??= PxFileConfiguration.Default; - Dictionary map = new() + Dictionary map = GetDimensionTypeTokenMap(conf); + + string cleanString = input.CleanStringDelimeters(conf.Symbols.Value.StringDelimeter); + if (map.TryGetValue(cleanString, out DimensionType value)) { - {conf.Tokens.VariableTypes.Content, DimensionType.Content}, - {conf.Tokens.VariableTypes.Contents, DimensionType.Content}, // Known alias for content dimension type - {conf.Tokens.VariableTypes.Time, DimensionType.Time}, - {conf.Tokens.VariableTypes.Ordinal, DimensionType.Ordinal}, - {conf.Tokens.VariableTypes.Nominal, DimensionType.Nominal}, - {conf.Tokens.VariableTypes.Geographical, DimensionType.Geographical}, - {conf.Tokens.VariableTypes.Region, DimensionType.Geographical}, // Known alias for geographical dimension type - {conf.Tokens.VariableTypes.Other, DimensionType.Other}, - {conf.Tokens.VariableTypes.Unknown, DimensionType.Unknown} + return value; + } + + return DimensionType.Unknown; + } + + private static Dictionary GetDimensionTypeTokenMap(PxFileConfiguration conf) + { + Dictionary map = []; + Dictionary tokensByType = new() + { + { DimensionType.Content, conf.Tokens.VariableTypes.Content }, + { DimensionType.Time, conf.Tokens.VariableTypes.Time }, + { DimensionType.Ordinal, conf.Tokens.VariableTypes.Ordinal }, + { DimensionType.Nominal, conf.Tokens.VariableTypes.Nominal }, + { DimensionType.Geographical, conf.Tokens.VariableTypes.Geographical }, + { DimensionType.Other, conf.Tokens.VariableTypes.Other }, + { DimensionType.Unknown, conf.Tokens.VariableTypes.Unknown } }; - string cleanString = input.CleanStringDelimeters(conf.Symbols.Value.StringDelimeter); - if (map.TryGetValue(cleanString, out DimensionType value)) return value; - else return DimensionType.Unknown; + foreach (KeyValuePair tokenSet in tokensByType) + { + foreach (string token in tokenSet.Value) + { + map[token] = tokenSet.Key; + } + } + + return map; } } } diff --git a/Px.Utils/PxFile/PxFileConfiguration.cs b/Px.Utils/PxFile/PxFileConfiguration.cs index ce244a54..da2ba25c 100644 --- a/Px.Utils/PxFile/PxFileConfiguration.cs +++ b/Px.Utils/PxFile/PxFileConfiguration.cs @@ -107,29 +107,31 @@ private TimeValue() { } public static TimeValue DefaultTimeValue => new(); } + /// + /// Defines accepted tokens for parsing and validating dimension types. + /// The first token in each array is treated as the primary value, while additional tokens are treated as aliases. + /// public class VariableTypeTokens { private const string CONTENT = "Content"; - private const string CONTENTS = "Contents"; + private const string CONTENTS_ALIAS = "Contents"; private const string TIME = "Time"; private const string ORDINAL = "Ordinal"; private const string NOMINAL = "Nominal"; private const string GEOGRAPHICAL = "Geographical"; - private const string REGION = "Region"; + private const string REGION_ALIAS = "Region"; private const string OTHER = "Other"; private const string UNKNOWN = "Unknown"; private const string CLASSIFICATORY = "Classificatory"; - public string Content { get; set; } = CONTENT; - public string Contents { get; set; } = CONTENTS; - public string Time { get; set; } = TIME; - public string Ordinal { get; set; } = ORDINAL; - public string Nominal { get; set; } = NOMINAL; - public string Geographical { get; set; } = GEOGRAPHICAL; - public string Region { get; set; } = REGION; - public string Other { get; set; } = OTHER; - public string Unknown { get; set; } = UNKNOWN; - public string Classificatory { get; set; } = CLASSIFICATORY; + public string[] Content { get; set; } = [CONTENT, CONTENTS_ALIAS]; + public string[] Time { get; set; } = [TIME]; + public string[] Ordinal { get; set; } = [ORDINAL]; + public string[] Nominal { get; set; } = [NOMINAL]; + public string[] Geographical { get; set; } = [GEOGRAPHICAL, REGION_ALIAS]; + public string[] Other { get; set; } = [OTHER]; + public string[] Unknown { get; set; } = [UNKNOWN]; + public string[] Classificatory { get; set; } = [CLASSIFICATORY]; private VariableTypeTokens() { } diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index 8f71281d..c7926312 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -1,3 +1,4 @@ +using Px.Utils.PxFile; using Px.Utils.Validation.SyntaxValidation; using System.Globalization; @@ -322,31 +323,19 @@ public sealed partial class ContentValidator { string[] allowedCharsets = ["ANSI", "Unicode"]; - string[] dimensionTypes = [ - validator.Conf.Tokens.VariableTypes.Content, - validator.Conf.Tokens.VariableTypes.Time, - validator.Conf.Tokens.VariableTypes.Geographical, - validator.Conf.Tokens.VariableTypes.Ordinal, - validator.Conf.Tokens.VariableTypes.Nominal, - validator.Conf.Tokens.VariableTypes.Other, - validator.Conf.Tokens.VariableTypes.Unknown, - validator.Conf.Tokens.VariableTypes.Classificatory - ]; - - string[] knownDimensionTypeAliases = [ - validator.Conf.Tokens.VariableTypes.Contents, - validator.Conf.Tokens.VariableTypes.Region, - ]; + HashSet primaryDimensionTypes = GetPrimaryDimensionTypeTokens(validator.Conf); + HashSet aliasDimensionTypes = GetAliasDimensionTypeTokens(validator.Conf); string value = SyntaxValidationUtilityMethods.CleanString(entry.Value, validator.Conf); if ((entry.Key.Keyword == validator.Conf.Tokens.KeyWords.Charset && !allowedCharsets.Contains(value)) || (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.CodePage && !value.Equals(validator._encoding.BodyName, StringComparison.OrdinalIgnoreCase)) || - (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && !dimensionTypes.Contains(value))) + (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && !primaryDimensionTypes.Contains(value) && !aliasDimensionTypes.Contains(value)) || + (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && aliasDimensionTypes.Contains(value))) { - // If the value is included in known aliases, set level to warning instead of error - ValidationFeedbackLevel level = entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && knownDimensionTypeAliases.Contains(value) ? - ValidationFeedbackLevel.Warning : - ValidationFeedbackLevel.Error; + // Using a known alias dimension type as a value for DimensionType is not invalid but is not recommended, so it is reported as a warning instead of an error + ValidationFeedbackLevel level = entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && aliasDimensionTypes.Contains(value) + ? ValidationFeedbackLevel.Warning + : ValidationFeedbackLevel.Error; KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( entry.KeyStartLineIndex, @@ -473,5 +462,40 @@ validator._dimensionValueNames is null || } return null; } + + private static HashSet GetPrimaryDimensionTypeTokens(PxFileConfiguration conf) + { + IEnumerable primaryTokens = GetDimensionTypeTokenSets(conf) + .Select(tokens => tokens.FirstOrDefault()) + .Where(token => !string.IsNullOrWhiteSpace(token)) + .Cast(); + + return [.. primaryTokens]; + } + + private static HashSet GetAliasDimensionTypeTokens(PxFileConfiguration conf) + { + IEnumerable aliasTokens = GetDimensionTypeTokenSets(conf) + .Where(tokens => tokens.Length > 1) + .SelectMany(tokens => tokens.Skip(1)) + .Where(token => !string.IsNullOrWhiteSpace(token)); + + return [.. aliasTokens]; + } + + private static IEnumerable GetDimensionTypeTokenSets(PxFileConfiguration conf) + { + return + [ + conf.Tokens.VariableTypes.Content, + conf.Tokens.VariableTypes.Time, + conf.Tokens.VariableTypes.Ordinal, + conf.Tokens.VariableTypes.Nominal, + conf.Tokens.VariableTypes.Geographical, + conf.Tokens.VariableTypes.Other, + conf.Tokens.VariableTypes.Unknown, + conf.Tokens.VariableTypes.Classificatory + ]; + } } } diff --git a/docs/architecture.models.md b/docs/architecture.models.md index 7705f8cc..9c6143bc 100644 --- a/docs/architecture.models.md +++ b/docs/architecture.models.md @@ -62,6 +62,8 @@ Task BuildAsync(IAsyncEnumerable> m Helpers: `MetadataEntryKeyBuilder`, `ValueParserUtilities`, `MetadataEntryKey`. +`PxFileConfiguration.TokenDefinitions.VariableTypeTokens` exposes configurable string arrays for each dimension type. The first value is the primary token used by builders, and any additional values are treated as aliases during parsing. + ## Extension Methods | File | Purpose | diff --git a/docs/architecture.validation.md b/docs/architecture.validation.md index 1873220f..84736d06 100644 --- a/docs/architecture.validation.md +++ b/docs/architecture.validation.md @@ -34,6 +34,8 @@ Validates metadata content (required keys, language definitions, dimension consi File: `Validation/ContentValidation/ContentValidator.cs` Partial files: `ValidationEntryFunctions.cs`, `ValidationFindKeywordFunctions.cs`, `UtilityMethods.cs` +Dimension type validation uses `PxFileConfiguration.TokenDefinitions.VariableTypeTokens`. The first configured token for each dimension type is treated as the recommended primary value, while additional configured tokens are accepted as aliases and reported as warnings. + ### DataValidator Validates data section (row counts, row lengths, value types, separators). From a509e408881319916dbb314f3781ae30ad84a2a6 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Thu, 30 Apr 2026 10:19:09 +0300 Subject: [PATCH 04/13] Cleanup --- ...ntentValidator.ValidationEntryFunctions.cs | 155 ++++++++++-------- docs/architecture.models.md | 2 +- 2 files changed, 85 insertions(+), 72 deletions(-) diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index c7926312..99cc464e 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -1,5 +1,6 @@ using Px.Utils.PxFile; using Px.Utils.Validation.SyntaxValidation; +using System.ComponentModel.DataAnnotations; using System.Globalization; namespace Px.Utils.Validation.ContentValidation @@ -318,71 +319,81 @@ public sealed partial class ContentValidator /// /// Entry in the Px file metadata. Represented by a object /// object that stores information that is gathered during the validation process - /// Key value pair containing information about the rule violation is returned if an unexpected value is detected + /// Key value pair containing information about the rule violation is returned if an unexpected or unrecommended value is detected from the entry public static ValidationFeedback? ValidateValueContents(ValidationStructuredEntry entry, ContentValidator validator) { - string[] allowedCharsets = ["ANSI", "Unicode"]; - - HashSet primaryDimensionTypes = GetPrimaryDimensionTypeTokens(validator.Conf); - HashSet aliasDimensionTypes = GetAliasDimensionTypeTokens(validator.Conf); - + string[] recommendedValueContents = []; + string[] knownAliases = []; + StringComparer comparer = StringComparer.Ordinal; + string keyword = entry.Key.Keyword; string value = SyntaxValidationUtilityMethods.CleanString(entry.Value, validator.Conf); - if ((entry.Key.Keyword == validator.Conf.Tokens.KeyWords.Charset && !allowedCharsets.Contains(value)) || - (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.CodePage && !value.Equals(validator._encoding.BodyName, StringComparison.OrdinalIgnoreCase)) || - (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && !primaryDimensionTypes.Contains(value) && !aliasDimensionTypes.Contains(value)) || - (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && aliasDimensionTypes.Contains(value))) - { - // Using a known alias dimension type as a value for DimensionType is not invalid but is not recommended, so it is reported as a warning instead of an error - ValidationFeedbackLevel level = entry.Key.Keyword == validator.Conf.Tokens.KeyWords.DimensionType && aliasDimensionTypes.Contains(value) - ? ValidationFeedbackLevel.Warning - : ValidationFeedbackLevel.Error; - - KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( - entry.KeyStartLineIndex, - entry.ValueStartIndex, - entry.LineChangeIndexes); - KeyValuePair feedback = new( - new(level, - ValidationFeedbackRule.InvalidValueFound), - new(validator._filename, - feedbackIndexes.Key, - feedbackIndexes.Value, - $"{entry.Key.Keyword}: {entry.Value}") - ); - - return new(feedback); + if (keyword == validator.Conf.Tokens.KeyWords.Charset) + { + recommendedValueContents = ["ANSI", "Unicode"]; + } + else if (keyword == validator.Conf.Tokens.KeyWords.CodePage) + { + recommendedValueContents = [validator._encoding.BodyName]; + comparer = StringComparer.OrdinalIgnoreCase; + } + else if (keyword == validator.Conf.Tokens.KeyWords.DimensionType) + { + (recommendedValueContents, knownAliases) = BuildAllowedDimensionTypes(validator.Conf); } - else if (entry.Key.Keyword == validator.Conf.Tokens.KeyWords.ContentVariableIdentifier) + else if (keyword == validator.Conf.Tokens.KeyWords.ContentVariableIdentifier) { string defaultLanguage = validator._defaultLanguage ?? string.Empty; string lang = entry.Key.Language ?? defaultLanguage; - if (validator._stubDimensionNames is not null && validator._stubDimensionNames.TryGetValue(lang, out string[]? stubValues) && - !Array.Exists(stubValues, d => d == value) && - (validator._headingDimensionNames is not null && validator._headingDimensionNames.TryGetValue(lang, out string[]? headingValues) && - !Array.Exists(headingValues, d => d == value))) - { - KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( - entry.KeyStartLineIndex, - entry.ValueStartIndex, - entry.LineChangeIndexes); + List dimensionNames = []; - KeyValuePair feedback = new( - new(ValidationFeedbackLevel.Error, - ValidationFeedbackRule.InvalidValueFound), - new(validator._filename, - feedbackIndexes.Key, - feedbackIndexes.Value, - $"{entry.Key.Keyword}: {entry.Value}") - ); + if (validator._stubDimensionNames is not null && validator._stubDimensionNames.TryGetValue(lang, out string[]? stubValues)) + { + dimensionNames.AddRange(stubValues); + } - return new(feedback); + if (validator._headingDimensionNames is not null && validator._headingDimensionNames.TryGetValue(lang, out string[]? headingValues)) + { + dimensionNames.AddRange(headingValues); } + + recommendedValueContents = [.. dimensionNames]; + } + + if (recommendedValueContents.Length > 0 && !recommendedValueContents.Contains(value, comparer)) + { + ValidationFeedbackLevel level = knownAliases.Contains(value, comparer) + ? ValidationFeedbackLevel.Warning + : ValidationFeedbackLevel.Error; + + return CreateInvalidValueFeedback(entry, validator, level); } return null; } + private static ValidationFeedback CreateInvalidValueFeedback( + ValidationStructuredEntry entry, + ContentValidator validator, + ValidationFeedbackLevel level) + { + KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( + entry.KeyStartLineIndex, + entry.ValueStartIndex, + entry.LineChangeIndexes); + + KeyValuePair feedback = new( + new(level, + ValidationFeedbackRule.InvalidValueFound), + new(validator._filename, + feedbackIndexes.Key, + feedbackIndexes.Value, + $"{entry.Key.Keyword}: {entry.Value}") + ); + + return new(feedback); + } + /// /// Validates that entries with specific keywords have the correct amount of values in the Px file metadata entry /// @@ -463,29 +474,11 @@ validator._dimensionValueNames is null || return null; } - private static HashSet GetPrimaryDimensionTypeTokens(PxFileConfiguration conf) + private static (string[] RecommendedValueContents, string[] KnownAliases) BuildAllowedDimensionTypes(PxFileConfiguration conf) { - IEnumerable primaryTokens = GetDimensionTypeTokenSets(conf) - .Select(tokens => tokens.FirstOrDefault()) - .Where(token => !string.IsNullOrWhiteSpace(token)) - .Cast(); - - return [.. primaryTokens]; - } - - private static HashSet GetAliasDimensionTypeTokens(PxFileConfiguration conf) - { - IEnumerable aliasTokens = GetDimensionTypeTokenSets(conf) - .Where(tokens => tokens.Length > 1) - .SelectMany(tokens => tokens.Skip(1)) - .Where(token => !string.IsNullOrWhiteSpace(token)); - - return [.. aliasTokens]; - } - - private static IEnumerable GetDimensionTypeTokenSets(PxFileConfiguration conf) - { - return + List primaryDimensionTypes = []; + List aliasDimensionTypes = []; + string[][] dimensionTypeTokenSets = [ conf.Tokens.VariableTypes.Content, conf.Tokens.VariableTypes.Time, @@ -496,6 +489,26 @@ private static IEnumerable GetDimensionTypeTokenSets(PxFileConfigurati conf.Tokens.VariableTypes.Unknown, conf.Tokens.VariableTypes.Classificatory ]; + + foreach (string[] tokenSet in dimensionTypeTokenSets) + { + if (tokenSet.Length == 0 || string.IsNullOrWhiteSpace(tokenSet[0])) + { + continue; + } + + primaryDimensionTypes.Add(tokenSet[0]); + + for (int i = 1; i < tokenSet.Length; i++) + { + if (!string.IsNullOrWhiteSpace(tokenSet[i])) + { + aliasDimensionTypes.Add(tokenSet[i]); + } + } + } + + return ([.. primaryDimensionTypes], [.. aliasDimensionTypes]); } } } diff --git a/docs/architecture.models.md b/docs/architecture.models.md index 9c6143bc..2ebfaa53 100644 --- a/docs/architecture.models.md +++ b/docs/architecture.models.md @@ -62,7 +62,7 @@ Task BuildAsync(IAsyncEnumerable> m Helpers: `MetadataEntryKeyBuilder`, `ValueParserUtilities`, `MetadataEntryKey`. -`PxFileConfiguration.TokenDefinitions.VariableTypeTokens` exposes configurable string arrays for each dimension type. The first value is the primary token used by builders, and any additional values are treated as aliases during parsing. +`PxFileConfiguration.TokenDefinitions.VariableTypeTokens` exposes configurable string arrays for each dimension type. The first value is the primary recommended token for validation and parsing, and any additional values are treated as aliases during parsing. ## Extension Methods From fff7b9d5dab64c624f8fb3e2dafec842cd177e54 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Thu, 30 Apr 2026 10:36:45 +0300 Subject: [PATCH 05/13] More cleanup --- .../ContentValidator.ValidationEntryFunctions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index 99cc464e..77c17aee 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -1,6 +1,5 @@ using Px.Utils.PxFile; using Px.Utils.Validation.SyntaxValidation; -using System.ComponentModel.DataAnnotations; using System.Globalization; namespace Px.Utils.Validation.ContentValidation From 59688b00eb1299acf080087a3ef6afee3728f096 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Mon, 1 Jun 2026 11:26:39 +0300 Subject: [PATCH 06/13] Simpler implementation for accepted dimension type values --- .../StringToDimensionTypeTests.cs | 2 +- .../ContentValidationTests.cs | 65 +++++++++++++------ .../Fixtures/ContentValidationFixtures.cs | 6 -- .../ModelBuilders/ValueParserUtilities.cs | 30 +-------- Px.Utils/PxFile/PxFileConfiguration.cs | 37 +++++------ ...ntentValidator.ValidationEntryFunctions.cs | 51 ++++----------- 6 files changed, 77 insertions(+), 114 deletions(-) diff --git a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs index e7d4db91..a48fa7df 100644 --- a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs +++ b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs @@ -39,7 +39,7 @@ public void CustomAliasTest() { string input = "Ranking"; PxFileConfiguration conf = PxFileConfiguration.Default; - conf.Tokens.VariableTypes.Ordinal = ["Ordinal", "Ranking"]; + conf.Tokens.VariableTypes.Mappings["Ranking"] = DimensionType.Ordinal; DimensionType expected = DimensionType.Ordinal; DimensionType actual = ValueParserUtilities.StringToDimensionType(input, conf); diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index 4054f81f..9b13e776 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -3,6 +3,7 @@ using Px.Utils.Validation.ContentValidation; using Px.Utils.Validation.SyntaxValidation; using Px.Utils.PxFile; +using Px.Utils.Models.Metadata.Enums; using System.Text; using System.Reflection; @@ -538,32 +539,59 @@ public void ValidateValueTypesCalledWithStructuredEntryArrayWithWrongValuesRetur } [TestMethod] - public void ValidateValueContentsCalledWithStructuredEntryArrayWithKnownDimensionTypeAliasesReturnsWithWarnings() + public void ValidateValueContentsCalledWithDimensionTypeEnumValueReturnsWithoutFeedback() { // Arrange - ValidationStructuredEntry[] entries = ContentValidationFixtures.STRUCTURED_ENTRY_ARRAY_WITH_KNOWN_DIMENSIONTYPE_ALIASES; - ContentValidator validator = new(filename, encoding, entries); + PxFileConfiguration conf = PxFileConfiguration.Default; + + ValidationStructuredEntry entry = new( + filename, + new ValidationStructuredEntryKey("VARIABLE-TYPE", "fi", "foo"), + "Content", + 0, + [], + 0, + Utils.Validation.ValueType.StringValue); + + ContentValidator validator = new(filename, encoding, [entry], conf: conf); + // Act - foreach (ValidationStructuredEntry entry in entries) - { - ValidationFeedback? result = ContentValidator.ValidateValueContents( - entry, - validator - ); - // Assert - Assert.IsNotNull(result); - Assert.HasCount(1, result); - Assert.AreEqual(ValidationFeedbackRule.InvalidValueFound, result.First().Key.Rule); - Assert.AreEqual(ValidationFeedbackLevel.Warning, result.First().Key.Level); - } + ValidationFeedback? result = ContentValidator.ValidateValueContents(entry, validator); + + // Assert + Assert.IsNull(result); + } + + [TestMethod] + public void ValidateValueContentsCalledWithCustomDimensionTypeValueReturnsWithoutFeedback() + { + // Arrange + PxFileConfiguration conf = PxFileConfiguration.Default; + conf.Tokens.VariableTypes.Mappings["Ranking"] = DimensionType.Ordinal; + + ValidationStructuredEntry entry = new( + filename, + new ValidationStructuredEntryKey("VARIABLE-TYPE", "fi", "foo"), + "Ranking", + 0, + [], + 0, + Utils.Validation.ValueType.StringValue); + + ContentValidator validator = new(filename, encoding, [entry], conf: conf); + + // Act + ValidationFeedback? result = ContentValidator.ValidateValueContents(entry, validator); + + // Assert + Assert.IsNull(result); } [TestMethod] - public void ValidateValueContentsCalledWithConfiguredDimensionTypeAliasReturnsWithWarning() + public void ValidateValueContentsCalledWithUnknownDimensionTypeValueReturnsWithoutFeedback() { // Arrange PxFileConfiguration conf = PxFileConfiguration.Default; - conf.Tokens.VariableTypes.Ordinal = ["Ordinal", "Ranking"]; ValidationStructuredEntry entry = new( filename, @@ -572,7 +600,7 @@ public void ValidateValueContentsCalledWithConfiguredDimensionTypeAliasReturnsWi 0, [], 0, - Px.Utils.Validation.ValueType.StringValue); + Utils.Validation.ValueType.StringValue); ContentValidator validator = new(filename, encoding, [entry], conf: conf); @@ -583,7 +611,6 @@ public void ValidateValueContentsCalledWithConfiguredDimensionTypeAliasReturnsWi Assert.IsNotNull(result); Assert.HasCount(1, result); Assert.AreEqual(ValidationFeedbackRule.InvalidValueFound, result.First().Key.Rule); - Assert.AreEqual(ValidationFeedbackLevel.Warning, result.First().Key.Level); } [TestMethod] diff --git a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs index ae4ee466..d69aec8b 100644 --- a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs +++ b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs @@ -755,12 +755,6 @@ internal static class ContentValidationFixtures Utils.Validation.ValueType.StringValue), ]; - internal static ValidationStructuredEntry[] STRUCTURED_ENTRY_ARRAY_WITH_KNOWN_DIMENSIONTYPE_ALIASES => - [ - new ValidationStructuredEntry(filename, dimensionTypeEntryKey, "Contents", 0, [], 0, Utils.Validation.ValueType.StringValue), - new ValidationStructuredEntry(filename, dimensionTypeEntryKey, "Region", 1, [], 0, Utils.Validation.ValueType.StringValue), - ]; - internal static ValidationStructuredEntry StructuredEntryWithUnmatchingAmountOfElements => new(filename, codesBarEntry.Key, diff --git a/Px.Utils/ModelBuilders/ValueParserUtilities.cs b/Px.Utils/ModelBuilders/ValueParserUtilities.cs index 1a9792a5..86c9604a 100644 --- a/Px.Utils/ModelBuilders/ValueParserUtilities.cs +++ b/Px.Utils/ModelBuilders/ValueParserUtilities.cs @@ -102,7 +102,7 @@ public static string GetTimeValValueRangeString(string input, PxFileConfiguratio /// /// Parses a string into a enumeration value. /// This method maps the input string to a enumeration value based on the provided or default PxFileConfiguration configuration. - /// The first configured token for a dimension type is treated as the primary value and any additional configured tokens are treated as aliases. + /// Mapping is done using the configured variable type mappings. /// If the input string does not map to a known , the method returns . /// /// The string to parse into a enumeration value. @@ -111,40 +111,14 @@ public static string GetTimeValValueRangeString(string input, PxFileConfiguratio public static DimensionType StringToDimensionType(string input, PxFileConfiguration? conf = null) { conf ??= PxFileConfiguration.Default; - Dictionary map = GetDimensionTypeTokenMap(conf); string cleanString = input.CleanStringDelimeters(conf.Symbols.Value.StringDelimeter); - if (map.TryGetValue(cleanString, out DimensionType value)) + if (conf.Tokens.VariableTypes.Mappings.TryGetValue(cleanString, out DimensionType value)) { return value; } return DimensionType.Unknown; } - - private static Dictionary GetDimensionTypeTokenMap(PxFileConfiguration conf) - { - Dictionary map = []; - Dictionary tokensByType = new() - { - { DimensionType.Content, conf.Tokens.VariableTypes.Content }, - { DimensionType.Time, conf.Tokens.VariableTypes.Time }, - { DimensionType.Ordinal, conf.Tokens.VariableTypes.Ordinal }, - { DimensionType.Nominal, conf.Tokens.VariableTypes.Nominal }, - { DimensionType.Geographical, conf.Tokens.VariableTypes.Geographical }, - { DimensionType.Other, conf.Tokens.VariableTypes.Other }, - { DimensionType.Unknown, conf.Tokens.VariableTypes.Unknown } - }; - - foreach (KeyValuePair tokenSet in tokensByType) - { - foreach (string token in tokenSet.Value) - { - map[token] = tokenSet.Key; - } - } - - return map; - } } } diff --git a/Px.Utils/PxFile/PxFileConfiguration.cs b/Px.Utils/PxFile/PxFileConfiguration.cs index da2ba25c..7e596b6a 100644 --- a/Px.Utils/PxFile/PxFileConfiguration.cs +++ b/Px.Utils/PxFile/PxFileConfiguration.cs @@ -108,33 +108,28 @@ private TimeValue() { } } /// - /// Defines accepted tokens for parsing and validating dimension types. - /// The first token in each array is treated as the primary value, while additional tokens are treated as aliases. + /// Defines accepted tokens for parsing and validating dimension types using an expandable dictionary string to . /// public class VariableTypeTokens { - private const string CONTENT = "Content"; - private const string CONTENTS_ALIAS = "Contents"; - private const string TIME = "Time"; - private const string ORDINAL = "Ordinal"; - private const string NOMINAL = "Nominal"; - private const string GEOGRAPHICAL = "Geographical"; - private const string REGION_ALIAS = "Region"; - private const string OTHER = "Other"; - private const string UNKNOWN = "Unknown"; - private const string CLASSIFICATORY = "Classificatory"; - - public string[] Content { get; set; } = [CONTENT, CONTENTS_ALIAS]; - public string[] Time { get; set; } = [TIME]; - public string[] Ordinal { get; set; } = [ORDINAL]; - public string[] Nominal { get; set; } = [NOMINAL]; - public string[] Geographical { get; set; } = [GEOGRAPHICAL, REGION_ALIAS]; - public string[] Other { get; set; } = [OTHER]; - public string[] Unknown { get; set; } = [UNKNOWN]; - public string[] Classificatory { get; set; } = [CLASSIFICATORY]; + /// + /// Gets or sets the expandable dictionary mapping string tokens to dimension types. + /// + public Dictionary Mappings { get; set; } = new(StringComparer.Ordinal) + { + { "Time", DimensionType.Time }, + { "Contents", DimensionType.Content }, + { "Geographical", DimensionType.Geographical }, + { "Ordinal", DimensionType.Ordinal }, + { "Nominal", DimensionType.Nominal }, + { "Other", DimensionType.Other }, + }; private VariableTypeTokens() { } + /// + /// Gets the default variable type tokens configuration. + /// public static VariableTypeTokens DefaultVariableTypeTokens => new(); } diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index 77c17aee..c1a3018a 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -1,5 +1,5 @@ -using Px.Utils.PxFile; using Px.Utils.Validation.SyntaxValidation; +using Px.Utils.Models.Metadata.Enums; using System.Globalization; namespace Px.Utils.Validation.ContentValidation @@ -338,7 +338,17 @@ public sealed partial class ContentValidator } else if (keyword == validator.Conf.Tokens.KeyWords.DimensionType) { - (recommendedValueContents, knownAliases) = BuildAllowedDimensionTypes(validator.Conf); + if (Enum.TryParse(value, out DimensionType enumType) && value == enumType.ToString()) + { + return null; + } + + if (validator.Conf.Tokens.VariableTypes.Mappings.TryGetValue(value, out DimensionType mappedType)) + { + return null; + } + + return CreateInvalidValueFeedback(entry, validator, ValidationFeedbackLevel.Error); } else if (keyword == validator.Conf.Tokens.KeyWords.ContentVariableIdentifier) { @@ -472,42 +482,5 @@ validator._dimensionValueNames is null || } return null; } - - private static (string[] RecommendedValueContents, string[] KnownAliases) BuildAllowedDimensionTypes(PxFileConfiguration conf) - { - List primaryDimensionTypes = []; - List aliasDimensionTypes = []; - string[][] dimensionTypeTokenSets = - [ - conf.Tokens.VariableTypes.Content, - conf.Tokens.VariableTypes.Time, - conf.Tokens.VariableTypes.Ordinal, - conf.Tokens.VariableTypes.Nominal, - conf.Tokens.VariableTypes.Geographical, - conf.Tokens.VariableTypes.Other, - conf.Tokens.VariableTypes.Unknown, - conf.Tokens.VariableTypes.Classificatory - ]; - - foreach (string[] tokenSet in dimensionTypeTokenSets) - { - if (tokenSet.Length == 0 || string.IsNullOrWhiteSpace(tokenSet[0])) - { - continue; - } - - primaryDimensionTypes.Add(tokenSet[0]); - - for (int i = 1; i < tokenSet.Length; i++) - { - if (!string.IsNullOrWhiteSpace(tokenSet[i])) - { - aliasDimensionTypes.Add(tokenSet[i]); - } - } - } - - return ([.. primaryDimensionTypes], [.. aliasDimensionTypes]); - } } } From 44267235ed06f8bf0bc98ef09026f9a92de3a6fc Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Mon, 1 Jun 2026 12:04:18 +0300 Subject: [PATCH 07/13] Fixes validation and metadata building mismatch and tests --- .../StringToDimensionTypeTests.cs | 11 +-------- .../ContentValidationTests.cs | 24 ------------------- .../DatabaseValidatorTests.cs | 2 +- .../Fixtures/ContentValidationFixtures.cs | 5 +--- .../Validation/Fixtures/PxFileFixtures.cs | 10 ++++---- ...ntentValidator.ValidationEntryFunctions.cs | 5 ---- 6 files changed, 8 insertions(+), 49 deletions(-) diff --git a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs index a48fa7df..42629104 100644 --- a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs +++ b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs @@ -17,16 +17,7 @@ public void TimeTest() } [TestMethod] - public void ContentTest() - { - string input = "Content"; - DimensionType expected = DimensionType.Content; - DimensionType actual = ValueParserUtilities.StringToDimensionType(input); - Assert.AreEqual(expected, actual); - } - - [TestMethod] - public void ContentAliasTest() + public void ContentsTest() { string input = "Contents"; DimensionType expected = DimensionType.Content; diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index 9b13e776..519dbca2 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -538,30 +538,6 @@ public void ValidateValueTypesCalledWithStructuredEntryArrayWithWrongValuesRetur } } - [TestMethod] - public void ValidateValueContentsCalledWithDimensionTypeEnumValueReturnsWithoutFeedback() - { - // Arrange - PxFileConfiguration conf = PxFileConfiguration.Default; - - ValidationStructuredEntry entry = new( - filename, - new ValidationStructuredEntryKey("VARIABLE-TYPE", "fi", "foo"), - "Content", - 0, - [], - 0, - Utils.Validation.ValueType.StringValue); - - ContentValidator validator = new(filename, encoding, [entry], conf: conf); - - // Act - ValidationFeedback? result = ContentValidator.ValidateValueContents(entry, validator); - - // Assert - Assert.IsNull(result); - } - [TestMethod] public void ValidateValueContentsCalledWithCustomDimensionTypeValueReturnsWithoutFeedback() { diff --git a/Px.Utils.UnitTests/Validation/DatabaseValidation/DatabaseValidatorTests.cs b/Px.Utils.UnitTests/Validation/DatabaseValidation/DatabaseValidatorTests.cs index f9657cf5..4b6e2921 100644 --- a/Px.Utils.UnitTests/Validation/DatabaseValidation/DatabaseValidatorTests.cs +++ b/Px.Utils.UnitTests/Validation/DatabaseValidation/DatabaseValidatorTests.cs @@ -161,7 +161,7 @@ public async Task ValidateDatabasAsynceWithCustomFunctionsReturnsFeedback() IDatabaseValidator[] customValidators = [ new MockCustomDatabaseValidator() - ]; + ]; DatabaseValidator validator = new( "database_invalid", customPxFileValidators: customValidators, diff --git a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs index d69aec8b..eada5c99 100644 --- a/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs +++ b/Px.Utils.UnitTests/Validation/Fixtures/ContentValidationFixtures.cs @@ -306,7 +306,7 @@ internal static class ContentValidationFixtures private static readonly ValidationStructuredEntry variableTypeBarEntry = new(filename, new ValidationStructuredEntryKey("VARIABLE-TYPE", null, "bar"), - "Content", + "Contents", 23, [], 13, @@ -429,9 +429,6 @@ internal static class ContentValidationFixtures 17, Utils.Validation.ValueType.StringValue); - private static readonly ValidationStructuredEntryKey dimensionTypeEntryKey = - new ("VARIABLE-TYPE", "fi", "foo"); - internal static ValidationStructuredEntry[] MINIMAL_STRUCTURED_ENTRY_ARRAY => [ charsetEntry, diff --git a/Px.Utils.UnitTests/Validation/Fixtures/PxFileFixtures.cs b/Px.Utils.UnitTests/Validation/Fixtures/PxFileFixtures.cs index 542a4491..d28536eb 100644 --- a/Px.Utils.UnitTests/Validation/Fixtures/PxFileFixtures.cs +++ b/Px.Utils.UnitTests/Validation/Fixtures/PxFileFixtures.cs @@ -1,4 +1,4 @@ -namespace Px.Utils.UnitTests.Validation.Fixtures +namespace Px.Utils.UnitTests.Validation.Fixtures { internal static class PxFileFixtures { @@ -22,7 +22,7 @@ internal static class PxFileFixtures "\r\nCODES(\"Vuosi\")=\"2015\",\"2016\",\"2017\",\"2018\",\"2019\",\"2020\",\"2021\",\"2022\",\"2023\",\"2024\";" + "\r\nCODES(\"Tiedot\")=\"code-foo-val-a\",\"code-foo-val-b\";" + "\r\nVARIABLE-TYPE(\"Vuosi\")=\"Time\";" + - "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Content\";" + + "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Contents\";" + "\r\nPRECISION(\"Tiedot\",\"foo-val-a\")=1;" + "\r\nPRECISION(\"Tiedot\",\"foo-val-b\")=1;" + "\r\nLAST-UPDATED(\"Tiedot\",\"foo-val-a\")=\"20231101 08:00\";" + @@ -64,7 +64,7 @@ internal static class PxFileFixtures "\r\nCODES(\"Vuosi\")=\"2015\",\"2016\",\"2017\",\"2018\",\"2019\",\"2020\",\"2021\",\"2022\",\"2023\",\"2024\";" + "\r\nCODES(\"Tiedot\")=\"code-foo-val-a\",\"code-foo-val-b\";" + "\r\nVARIABLE-TYPE(\"Vuosi\")=\"Time\";" + - "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Content\";" + + "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Contents\";" + "\r\nPRECISION(\"Tiedot\",\"foo-val-a\")=1;" + "\r\nPRECISION(\"Tiedot\",\"foo-val-b\")=1;" + "\r\nLAST-UPDATED(\"Tiedot\",\"foo-val-a\")=\"20231101 08:00\";" + @@ -94,7 +94,7 @@ internal static class PxFileFixtures "\r\nCODES(\"Vuosi\")=\"2015\",\"2016\",\"2017\",\"2018\",\"2019\",\"2020\",\"2021\",\"2022\",\"2023\",\"2024\";" + "\r\nCODES(\"Tiedot\")=\"code-foo-val-a\",\"code-foo-val-b\";" + "\r\nVARIABLE-TYPE(\"Vuosi\")=\"Time\";" + - "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Content\";" + + "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Contents\";" + "\r\nPRECISION(\"Tiedot\",\"foo-val-a\")=1;" + "\r\nPRECISION(\"Tiedot\",\"foo-val-b\")=1;" + "\r\nLAST-UPDATED(\"Tiedot\",\"foo-val-a\")=\"20231101 08:00\";" + @@ -135,7 +135,7 @@ internal static class PxFileFixtures "\r\nCODES(\"Vuosi\")=\"2015\",\"2016\",\"2017\",\"2018\",\"2019\",\"2020\",\"2021\",\"2022\",\"2023\",\"2024\";" + "\r\nCODES(\"Tiedot\")=\"code-foo-val-a\",\"code-foo-val-b\";" + "\r\nVARIABLE-TYPE(\"Vuosi\")=\"Time\";" + - "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Content\";" + + "\r\nVARIABLE-TYPE(\"Tiedot\")=\"Contents\";" + "\r\nPRECISION(\"Tiedot\",\"foo-val-a\")=1;" + "\r\nLAST-UPDATED(\"Tiedot\",\"foo-val-a\")=\"20231101 08:00\";" + "\r\nUNITS(\"Tiedot\",\"foo-val-a\")=\"kpl\";" + diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index c1a3018a..e1099a93 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -338,11 +338,6 @@ public sealed partial class ContentValidator } else if (keyword == validator.Conf.Tokens.KeyWords.DimensionType) { - if (Enum.TryParse(value, out DimensionType enumType) && value == enumType.ToString()) - { - return null; - } - if (validator.Conf.Tokens.VariableTypes.Mappings.TryGetValue(value, out DimensionType mappedType)) { return null; From bf37c8f53946508ecd7e5506f813c71e321955b4 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Mon, 1 Jun 2026 12:07:43 +0300 Subject: [PATCH 08/13] Raise minor version number due to scale of changes --- Px.Utils/Px.Utils.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Px.Utils/Px.Utils.csproj b/Px.Utils/Px.Utils.csproj index bd2ca8d5..2e855475 100644 --- a/Px.Utils/Px.Utils.csproj +++ b/Px.Utils/Px.Utils.csproj @@ -2,7 +2,7 @@ Px.Utils - 1.4.1 + 1.5.0 net10.0 enable enable From 6744b0243be0cc7f3951834c1c4ef1f9ab86522d Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Tue, 2 Jun 2026 15:37:15 +0300 Subject: [PATCH 09/13] Update documentation and make dimension type mapping case insensitive --- .../StringToDimensionTypeTests.cs | 10 ++++++---- .../ContentValidationTests.cs | 10 +++++++--- Px.Utils.sln | 17 +++++++++++++++-- Px.Utils/ModelBuilders/ValueParserUtilities.cs | 1 + Px.Utils/PxFile/PxFileConfiguration.cs | 2 +- docs/PXFILE_SPECIFICATION.md | 3 ++- docs/architecture.models.md | 2 +- docs/architecture.overview.md | 1 + 8 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs index 42629104..85b97de8 100644 --- a/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs +++ b/Px.Utils.UnitTests/ModelBuilderTests/ValueParserUtilitiesTests/StringToDimensionTypeTests.cs @@ -1,6 +1,7 @@ using Px.Utils.ModelBuilders; using Px.Utils.Models.Metadata.Enums; using Px.Utils.PxFile; +using System.Globalization; namespace Px.Utils.UnitTests.ModelBuilderTests.ValueParserUtilitiesTests { @@ -26,13 +27,14 @@ public void ContentsTest() } [TestMethod] - public void CustomAliasTest() + [DataRow("RANKING", DimensionType.Ordinal)] + [DataRow("region", DimensionType.Geographical)] + public void CustomAliasTest(string alias, DimensionType expected) { - string input = "Ranking"; + string input = alias.ToUpper(CultureInfo.InvariantCulture); // Testing case-insensitivity PxFileConfiguration conf = PxFileConfiguration.Default; - conf.Tokens.VariableTypes.Mappings["Ranking"] = DimensionType.Ordinal; + conf.Tokens.VariableTypes.Mappings[alias] = expected; - DimensionType expected = DimensionType.Ordinal; DimensionType actual = ValueParserUtilities.StringToDimensionType(input, conf); Assert.AreEqual(expected, actual); } diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index 519dbca2..fa12843b 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -6,6 +6,7 @@ using Px.Utils.Models.Metadata.Enums; using System.Text; using System.Reflection; +using System.Globalization; namespace Px.Utils.UnitTests.Validation.ContentValidationTests { @@ -539,21 +540,24 @@ public void ValidateValueTypesCalledWithStructuredEntryArrayWithWrongValuesRetur } [TestMethod] - public void ValidateValueContentsCalledWithCustomDimensionTypeValueReturnsWithoutFeedback() + [DataRow("ranking", DimensionType.Ordinal)] + [DataRow("REGION", DimensionType.Geographical)] + public void ValidateValueContentsCalledWithCustomDimensionTypesValueReturnsWithoutFeedback(string alias, DimensionType dimensionType) { // Arrange PxFileConfiguration conf = PxFileConfiguration.Default; - conf.Tokens.VariableTypes.Mappings["Ranking"] = DimensionType.Ordinal; + conf.Tokens.VariableTypes.Mappings[alias] = dimensionType; ValidationStructuredEntry entry = new( filename, new ValidationStructuredEntryKey("VARIABLE-TYPE", "fi", "foo"), - "Ranking", + alias.ToUpper(CultureInfo.InvariantCulture), // The value is converted to upper case to verify case insensitivity of the dimension type matching 0, [], 0, Utils.Validation.ValueType.StringValue); + ContentValidator validator = new(filename, encoding, [entry], conf: conf); // Act diff --git a/Px.Utils.sln b/Px.Utils.sln index 4fe9d1ae..a111bbda 100644 --- a/Px.Utils.sln +++ b/Px.Utils.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34408.163 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11709.299 stable MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Px.Utils", "Px.Utils\Px.Utils.csproj", "{D94934AF-6B41-4C66-818A-C4C33230E9DD}" EndProject @@ -36,6 +36,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Px.Utils.TestingApp", "Px.U {D94934AF-6B41-4C66-818A-C4C33230E9DD} = {D94934AF-6B41-4C66-818A-C4C33230E9DD} EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "architecture", "architecture", "{BFA8E05B-EBD8-4085-B765-EAED69DD0874}" + ProjectSection(SolutionItems) = preProject + docs\architecture.md = docs\architecture.md + docs\architecture.models.md = docs\architecture.models.md + docs\architecture.operations.md = docs\architecture.operations.md + docs\architecture.overview.md = docs\architecture.overview.md + docs\architecture.readers.md = docs\architecture.readers.md + docs\architecture.serializers.md = docs\architecture.serializers.md + docs\architecture.testing.md = docs\architecture.testing.md + docs\architecture.validation.md = docs\architecture.validation.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,6 +72,7 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {EB4119B8-8292-418A-9CD3-2905DA67B889} = {BC1C9772-9B8F-49A7-A8F3-09E3A94789ED} + {BFA8E05B-EBD8-4085-B765-EAED69DD0874} = {42DFE3AF-7507-415A-806B-99B74519BF97} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {B1F81A3B-9FF5-4557-8A5A-A9519C42C2B2} diff --git a/Px.Utils/ModelBuilders/ValueParserUtilities.cs b/Px.Utils/ModelBuilders/ValueParserUtilities.cs index 86c9604a..8ff707e2 100644 --- a/Px.Utils/ModelBuilders/ValueParserUtilities.cs +++ b/Px.Utils/ModelBuilders/ValueParserUtilities.cs @@ -1,6 +1,7 @@ using Px.Utils.Models.Metadata.Enums; using Px.Utils.Models.Metadata.ExtensionMethods; using Px.Utils.PxFile; +using System.Globalization; namespace Px.Utils.ModelBuilders { diff --git a/Px.Utils/PxFile/PxFileConfiguration.cs b/Px.Utils/PxFile/PxFileConfiguration.cs index 7e596b6a..9b4f2f61 100644 --- a/Px.Utils/PxFile/PxFileConfiguration.cs +++ b/Px.Utils/PxFile/PxFileConfiguration.cs @@ -115,7 +115,7 @@ public class VariableTypeTokens /// /// Gets or sets the expandable dictionary mapping string tokens to dimension types. /// - public Dictionary Mappings { get; set; } = new(StringComparer.Ordinal) + public Dictionary Mappings { get; set; } = new(StringComparer.OrdinalIgnoreCase) { { "Time", DimensionType.Time }, { "Contents", DimensionType.Content }, diff --git a/docs/PXFILE_SPECIFICATION.md b/docs/PXFILE_SPECIFICATION.md index ac5ecb0e..8c3d0a65 100644 --- a/docs/PXFILE_SPECIFICATION.md +++ b/docs/PXFILE_SPECIFICATION.md @@ -96,7 +96,7 @@ Whitespace characters are not significant in the value outside of ```"``` separa - Decimal separator is period ```.```. - Thousands separator is not allowed. - Whitespace characters are not allowed between the characters of the number. -- Number must be in range of ±7.9228 x 10^28. +- Number must be in range of ±7.9228 x 10^28. #### List of strings - List items are separated by a comma ```,```. @@ -281,6 +281,7 @@ More spesific encoding information. The value must be a string that matches the - Recommended that the variable type is defined for each variable defined by the STUB or HEADING. - Has a set of allowed values: ```Content```, ```Time```, ```Geographical```, ```Ordinal```, ```Nominal```, ```Other```, ```Classificatory```., ```Unknown```. - Can be defined for each language, but this is not recommended. +- Value is not case sensitive, but uppercase characters are recommended. #### TIMEVAL - See the TIMEVAL entry for the syntax and content requirements. \ No newline at end of file diff --git a/docs/architecture.models.md b/docs/architecture.models.md index 2ebfaa53..d83ff085 100644 --- a/docs/architecture.models.md +++ b/docs/architecture.models.md @@ -62,7 +62,7 @@ Task BuildAsync(IAsyncEnumerable> m Helpers: `MetadataEntryKeyBuilder`, `ValueParserUtilities`, `MetadataEntryKey`. -`PxFileConfiguration.TokenDefinitions.VariableTypeTokens` exposes configurable string arrays for each dimension type. The first value is the primary recommended token for validation and parsing, and any additional values are treated as aliases during parsing. +`PxFileConfiguration.TokenDefinitions.VariableTypeTokens` exposes a configurable `Dictionary` (`Mappings`) used by both parsing and validation for `VARIABLE-TYPE` values. The dictionary uses `StringComparer.OrdinalIgnoreCase`, so lookups are case-insensitive by default. Custom aliases can be added by inserting additional keys into `Mappings`. ## Extension Methods diff --git a/docs/architecture.overview.md b/docs/architecture.overview.md index 3940ea1c..152f4c25 100644 --- a/docs/architecture.overview.md +++ b/docs/architecture.overview.md @@ -27,6 +27,7 @@ No DI container — all types instantiated directly via constructors. Configurat | `Symbols.KeywordSeparator` | Keyword = value separator | `=` | | `Symbols.EntrySeparator` | Entry separator | `;` | | `Tokens.KeyWords.Data` | Data section keyword | `DATA` | +| `Tokens.VariableTypes.Mappings` | `VARIABLE-TYPE` token to `DimensionType` mapping | Case-insensitive dictionary (`StringComparer.OrdinalIgnoreCase`) | ## Key Patterns From 6a3977c4b7c88ed9d5cd3de8d5e3335578fdf7ff Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Tue, 2 Jun 2026 15:40:30 +0300 Subject: [PATCH 10/13] Cleanup --- Px.Utils.sln | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Px.Utils.sln b/Px.Utils.sln index a111bbda..9196bd04 100644 --- a/Px.Utils.sln +++ b/Px.Utils.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 18 -VisualStudioVersion = 18.5.11709.299 stable +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34408.163 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Px.Utils", "Px.Utils\Px.Utils.csproj", "{D94934AF-6B41-4C66-818A-C4C33230E9DD}" EndProject From 3ee5b63462cfa486e2f7b8b421689aba5ea3c10a Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Tue, 2 Jun 2026 15:44:49 +0300 Subject: [PATCH 11/13] Invalid test name --- .../ContentValidationTests/ContentValidationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index fa12843b..b0db7c28 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -568,7 +568,7 @@ public void ValidateValueContentsCalledWithCustomDimensionTypesValueReturnsWitho } [TestMethod] - public void ValidateValueContentsCalledWithUnknownDimensionTypeValueReturnsWithoutFeedback() + public void ValidateValueContentsCalledWithUnknownDimensionTypeValueReturnsWithError() { // Arrange PxFileConfiguration conf = PxFileConfiguration.Default; @@ -591,6 +591,7 @@ public void ValidateValueContentsCalledWithUnknownDimensionTypeValueReturnsWitho Assert.IsNotNull(result); Assert.HasCount(1, result); Assert.AreEqual(ValidationFeedbackRule.InvalidValueFound, result.First().Key.Rule); + Assert.AreEqual(ValidationFeedbackLevel.Error, result.First().Key.Level); } [TestMethod] From 7ff57aeb07167b1301b75007ae62152a81a56b27 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Tue, 2 Jun 2026 15:57:41 +0300 Subject: [PATCH 12/13] Remove references to removed alias functionality in validation --- ...ntentValidator.ValidationEntryFunctions.cs | 30 ++++++++----------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs index e1099a93..5f0ab0ad 100644 --- a/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs +++ b/Px.Utils/Validation/ContentValidation/ContentValidator.ValidationEntryFunctions.cs @@ -318,32 +318,31 @@ public sealed partial class ContentValidator /// /// Entry in the Px file metadata. Represented by a object /// object that stores information that is gathered during the validation process - /// Key value pair containing information about the rule violation is returned if an unexpected or unrecommended value is detected from the entry + /// Key value pair containing information about the rule violation is returned if an unexpected value is detected from the entry public static ValidationFeedback? ValidateValueContents(ValidationStructuredEntry entry, ContentValidator validator) { - string[] recommendedValueContents = []; - string[] knownAliases = []; + string[] allowedValueContents = []; StringComparer comparer = StringComparer.Ordinal; string keyword = entry.Key.Keyword; string value = SyntaxValidationUtilityMethods.CleanString(entry.Value, validator.Conf); if (keyword == validator.Conf.Tokens.KeyWords.Charset) { - recommendedValueContents = ["ANSI", "Unicode"]; + allowedValueContents = ["ANSI", "Unicode"]; } else if (keyword == validator.Conf.Tokens.KeyWords.CodePage) { - recommendedValueContents = [validator._encoding.BodyName]; + allowedValueContents = [validator._encoding.BodyName]; comparer = StringComparer.OrdinalIgnoreCase; } else if (keyword == validator.Conf.Tokens.KeyWords.DimensionType) { - if (validator.Conf.Tokens.VariableTypes.Mappings.TryGetValue(value, out DimensionType mappedType)) + if (validator.Conf.Tokens.VariableTypes.Mappings.TryGetValue(value, out DimensionType _)) { return null; } - return CreateInvalidValueFeedback(entry, validator, ValidationFeedbackLevel.Error); + return CreateInvalidValueFeedback(entry, validator); } else if (keyword == validator.Conf.Tokens.KeyWords.ContentVariableIdentifier) { @@ -361,25 +360,20 @@ public sealed partial class ContentValidator dimensionNames.AddRange(headingValues); } - recommendedValueContents = [.. dimensionNames]; + allowedValueContents = [.. dimensionNames]; } - if (recommendedValueContents.Length > 0 && !recommendedValueContents.Contains(value, comparer)) + if (allowedValueContents.Length == 0 || allowedValueContents.Contains(value, comparer)) { - ValidationFeedbackLevel level = knownAliases.Contains(value, comparer) - ? ValidationFeedbackLevel.Warning - : ValidationFeedbackLevel.Error; - - return CreateInvalidValueFeedback(entry, validator, level); + return null; } - return null; + return CreateInvalidValueFeedback(entry, validator); } private static ValidationFeedback CreateInvalidValueFeedback( ValidationStructuredEntry entry, - ContentValidator validator, - ValidationFeedbackLevel level) + ContentValidator validator) { KeyValuePair feedbackIndexes = SyntaxValidationUtilityMethods.GetLineAndCharacterIndex( entry.KeyStartLineIndex, @@ -387,7 +381,7 @@ private static ValidationFeedback CreateInvalidValueFeedback( entry.LineChangeIndexes); KeyValuePair feedback = new( - new(level, + new(ValidationFeedbackLevel.Error, ValidationFeedbackRule.InvalidValueFound), new(validator._filename, feedbackIndexes.Key, From 47ac546cc5daad17faa2c255adfbb8a860dd9385 Mon Sep 17 00:00:00 2001 From: Sakari Malkki Date: Tue, 2 Jun 2026 15:58:51 +0300 Subject: [PATCH 13/13] Remove excess whitespace --- .../Validation/ContentValidationTests/ContentValidationTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs index b0db7c28..3fcc6928 100644 --- a/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs +++ b/Px.Utils.UnitTests/Validation/ContentValidationTests/ContentValidationTests.cs @@ -557,7 +557,6 @@ public void ValidateValueContentsCalledWithCustomDimensionTypesValueReturnsWitho 0, Utils.Validation.ValueType.StringValue); - ContentValidator validator = new(filename, encoding, [entry], conf: conf); // Act