From 39e1d7f29950a576f74ff417338bc5f4b6fd23fe Mon Sep 17 00:00:00 2001 From: Claudio Benghi Date: Sun, 5 May 2024 04:04:14 +0100 Subject: [PATCH] Improved value tests for attributes and properties. --- SolutionTooling/BuildingSmartRepoFiles.cs | 1 + .../XsTypes/IsValid.md | 2 +- .../IdsLib.IfcSchema/SchemaInfo.md | 1 + .../SchemaInfo/GetAttributesXmlTypes.md | 23 +++++++ .../IfcSchema_DocumentationGenerator.cs | 18 ++++-- ids-lib.codegen/Program.cs | 4 ++ ids-lib.codegen/XmlSchema_XsTypesGenerator.cs | 61 +++++++++++++++++++ ids-lib.codegen/buildingSMART/DataTypes.md | 27 ++++---- ids-lib/ErrorCodes.md | 1 + .../IdsSchema/IdsNodes/Facets/IdsAttribute.cs | 21 ++++--- .../IdsSchema/IdsNodes/Facets/IdsProperty.cs | 23 ++++--- ids-lib/IdsSchema/IdsXmlNode.cs | 12 ++++ ids-lib/IdsSchema/XsNodes/XsEnumeration.cs | 40 +----------- ids-lib/IdsSchema/XsNodes/XsRestriction.cs | 33 +++++----- ids-lib/IdsSchema/XsNodes/XsTypes.cs | 59 ++++++++++++++---- ids-lib/IdsSchema/XsNodes/XsTypes.g.cs | 20 ++++++ ids-lib/IfcSchema/SchemaInfo.cs | 17 +++++- ids-lib/LibraryInformation.cs | 2 +- ids-lib/Messages/IdsErrorMessages.cs | 10 ++- ids-lib/ids-lib.csproj | 2 +- ids-tool.tests/AuditTests.cs | 4 +- .../InvalidFiles/InvalidAttributeTypes.ids | 10 +++ ids-tool.tests/IssueTests.cs | 2 +- ids-tool/ids-tool.csproj | 2 +- 24 files changed, 288 insertions(+), 107 deletions(-) create mode 100644 ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo/GetAttributesXmlTypes.md create mode 100644 ids-lib.codegen/XmlSchema_XsTypesGenerator.cs create mode 100644 ids-lib/IdsSchema/XsNodes/XsTypes.g.cs diff --git a/SolutionTooling/BuildingSmartRepoFiles.cs b/SolutionTooling/BuildingSmartRepoFiles.cs index aed6107..8b705e0 100644 --- a/SolutionTooling/BuildingSmartRepoFiles.cs +++ b/SolutionTooling/BuildingSmartRepoFiles.cs @@ -85,6 +85,7 @@ public static IEnumerable GetIdsRepositoryTestCaseIdsFiles() var d = new DirectoryInfo(IdsRepositoryTestcasesPath); return GetFilesOrEmpty(d, "*.ids"); } + public static IEnumerable GetIdsRepositoryTestCaseIfcFiles() { // start from current directory and look in relative position for the bs IDS repository diff --git a/ids-lib-documentation/IdsLib.IdsSchema.XsNodes/XsTypes/IsValid.md b/ids-lib-documentation/IdsLib.IdsSchema.XsNodes/XsTypes/IsValid.md index a98ae71..82e7995 100644 --- a/ids-lib-documentation/IdsLib.IdsSchema.XsNodes/XsTypes/IsValid.md +++ b/ids-lib-documentation/IdsLib.IdsSchema.XsNodes/XsTypes/IsValid.md @@ -13,7 +13,7 @@ public static bool IsValid(string valueString, BaseTypes @base) ## Return Value -TRUE if compatible, false otherwise +TRUE if compatible, FALSE otherwise ## See Also diff --git a/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo.md b/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo.md index 3dda385..5c373d9 100644 --- a/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo.md +++ b/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo.md @@ -23,6 +23,7 @@ public class SchemaInfo : IEnumerable | [GetAttributeNames](SchemaInfo/GetAttributeNames.md)() | Returns all attribute names in the schema | | [GetAttributeRelations](SchemaInfo/GetAttributeRelations.md)(…) | Provides information of classes that have an attribute and the form of the relation to it. See for similar function with different return type. | | [GetAttributesTypes](SchemaInfo/GetAttributesTypes.md)(…) | Returns a distinct enumerable of the backing types of the required attributes, given a set of attribut names | +| [GetAttributesXmlTypes](SchemaInfo/GetAttributesXmlTypes.md)(…) | Returns a distinct enumerable of the backing types of the required attributes, given a set of attribut names | | [GetEnumerator](SchemaInfo/GetEnumerator.md)() | The default enumerator for the schema returns the classes defined within | | static [AllAttributes](SchemaInfo/AllAttributes.md) { get; } | The names of all attributes across all schemas. | | static [AllConcreteClasses](SchemaInfo/AllConcreteClasses.md) { get; } | The names of all concrete classes across known IFC schemas. | diff --git a/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo/GetAttributesXmlTypes.md b/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo/GetAttributesXmlTypes.md new file mode 100644 index 0000000..c6d3c7f --- /dev/null +++ b/ids-lib-documentation/IdsLib.IfcSchema/SchemaInfo/GetAttributesXmlTypes.md @@ -0,0 +1,23 @@ +# SchemaInfo.GetAttributesXmlTypes method + +Returns a distinct enumerable of the backing types of the required attributes, given a set of attribut names + +```csharp +public IEnumerable GetAttributesXmlTypes(IEnumerable attributeNames) +``` + +| parameter | description | +| --- | --- | +| attributeNames | The names of the attributes to evaluate | + +## Return Value + +string names of the types found in the evaluation of the attributes + +## See Also + +* enum [BaseTypes](../../IdsLib.IdsSchema.XsNodes/XsTypes.BaseTypes.md) +* class [SchemaInfo](../SchemaInfo.md) +* namespace [IdsLib.IfcSchema](../../ids-lib.md) + + diff --git a/ids-lib.codegen/IfcSchema_DocumentationGenerator.cs b/ids-lib.codegen/IfcSchema_DocumentationGenerator.cs index 5f93030..4da5f08 100644 --- a/ids-lib.codegen/IfcSchema_DocumentationGenerator.cs +++ b/ids-lib.codegen/IfcSchema_DocumentationGenerator.cs @@ -13,7 +13,7 @@ internal static string Execute(Dictionary dataTypeDictiona { var schemas = new string[] { "Ifc2x3", "Ifc4", "Ifc4x3" }; - StringBuilder sbDataTypes = new StringBuilder(); + var sbDataTypes = new StringBuilder(); foreach (var dataType in dataTypeDictionary.Values.OrderBy(x=>x.Name)) { var checks = schemas.Select(x => dataType.Schemas.Contains(x) ? "✔️ " : "❌ "); @@ -21,11 +21,12 @@ internal static string Execute(Dictionary dataTypeDictiona } - StringBuilder sbXmlTypes = new StringBuilder(); + var sbXmlTypes = new StringBuilder(); var xmlTypes = dataTypeDictionary.Values.Select(x => x.XmlBackingType).Where(str => !string.IsNullOrWhiteSpace(str)).Distinct(); foreach (var dataType in xmlTypes.OrderBy(x => x)) { - sbXmlTypes.AppendLine($"| {dataType,-11} |"); + var t = XmlSchema_XsTypesGenerator.GetRegexString(dataType).Replace("|", "|"); + sbXmlTypes.AppendLine($"| {dataType,-11} | {t,-72} |"); } var source = stub; @@ -49,12 +50,17 @@ Property dataTypes can be set to any values according to the following table. ## XML base types -The list of valid XML base types for the `base` attribute of `xs:restriction` is: +The list of valid XML base types for the `base` attribute of `xs:restriction`, and the associated regex expression to check for the validity of string representation is as follows: -| Base type | -| ----------- | +| Base type | string regex constraint | +| ----------- | ------------------------------------------------------------------------ | +For example: + +- To specify numbers: you must use a dot as the decimal separator, and not use a thousands separator (e.g. `4.2` is valid, but `1.234,5` is invalid). Scientific notation is allowed (e.g. `1e3` to represent `1000`). +- To specify boolean: valid values are `true` or `false`, `0`, or `1`. + ## Notes Please note, this document has been automatically generated via the IDS Audit Tool repository, any changes should be initiated there. diff --git a/ids-lib.codegen/Program.cs b/ids-lib.codegen/Program.cs index eedd6f9..75c9cb3 100644 --- a/ids-lib.codegen/Program.cs +++ b/ids-lib.codegen/Program.cs @@ -28,6 +28,10 @@ static void Main() IfcSchema_DocumentationGenerator.Execute(dataTypeDictionary), @"ids-lib.codegen\buildingSMART\DataTypes.md") | GeneratedContentChanged; + GeneratedContentChanged = EvaluateContentChanged( + XmlSchema_XsTypesGenerator.Execute(dataTypeDictionary), + @"ids-lib\IdsSchema\XsNodes\XsTypes.g.cs") | GeneratedContentChanged; + GeneratedContentChanged = EvaluateContentChanged( IfcSchema_ClassGenerator.Execute(), @"ids-lib\IfcSchema\SchemaInfo.Schemas.g.cs") | GeneratedContentChanged; diff --git a/ids-lib.codegen/XmlSchema_XsTypesGenerator.cs b/ids-lib.codegen/XmlSchema_XsTypesGenerator.cs new file mode 100644 index 0000000..21a9338 --- /dev/null +++ b/ids-lib.codegen/XmlSchema_XsTypesGenerator.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace IdsLib.codegen +{ + internal class XmlSchema_XsTypesGenerator + { + internal static string Execute(Dictionary dataTypeDictionary) + { + var sbXmlTypes = new StringBuilder(); + var xmlTypes = dataTypeDictionary.Values.Select(x => x.XmlBackingType).Where(str => !string.IsNullOrWhiteSpace(str)).Distinct(); + foreach (var dataType in xmlTypes.OrderBy(x => x)) + { + var t = XmlSchema_XsTypesGenerator.GetRegexString(dataType); + if (string.IsNullOrWhiteSpace(t)) + continue; + var name = dataType.Replace("xs:", ""); + name = name[..1].ToUpper() + name[1..]; + sbXmlTypes.AppendLine($"\t\tprivate readonly static Regex regex{name} = new(@\"{t}\", RegexOptions.Compiled);"); + } + + var source = stub; + source = source.Replace($"", sbXmlTypes.ToString().TrimEnd('\r', '\n')); + return source; + } + + internal static string GetRegexString(string dataType) + { + return dataType switch + { + "xs:boolean" => @"^(true|false|0|1)$", + "xs:date" => @"^\d{4}-\d{2}-\d{2}(Z|([+-]\d{2}:\d{2}))?$", + "xs:dateTime" => @"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", + "xs:double" => @"^([-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?|NaN|\+INF|-INF)$", + "xs:duration" => @"^[-+]?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$", + "xs:integer" => @"^[+-]?(\d+)$", + "xs:string" => @"^.?$", + "xs:time" => @"^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", + _ => "" + }; + } + + private const string stub = @"// +// This code was automatically generated. +// Any changes made to this file will be lost. + +using System.Text.RegularExpressions; + +namespace IdsLib.IdsSchema.XsNodes +{ + public static partial class XsTypes + { + + } +} +"; + } +} diff --git a/ids-lib.codegen/buildingSMART/DataTypes.md b/ids-lib.codegen/buildingSMART/DataTypes.md index dee7112..268f423 100644 --- a/ids-lib.codegen/buildingSMART/DataTypes.md +++ b/ids-lib.codegen/buildingSMART/DataTypes.md @@ -403,18 +403,23 @@ Columns of the table determine the validity of the type depending on the schema ## XML base types -The list of valid XML base types for the `base` attribute of `xs:restriction` is: +The list of valid XML base types for the `base` attribute of `xs:restriction`, and the associated regex expression to check for the validity of string representation is as follows: -| Base type | -| ----------- | -| xs:boolean | -| xs:date | -| xs:dateTime | -| xs:double | -| xs:duration | -| xs:integer | -| xs:string | -| xs:time | +| Base type | string regex constraint | +| ----------- | ------------------------------------------------------------------------ | +| xs:boolean | ^(true|false|0|1)$ | +| xs:date | ^\d{4}-\d{2}-\d{2}(Z|([+-]\d{2}:\d{2}))?$ | +| xs:dateTime | ^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$ | +| xs:double | ^([-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?|NaN|\+INF|-INF)$ | +| xs:duration | ^[-+]?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$ | +| xs:integer | ^[+-]?(\d+)$ | +| xs:string | ^.?$ | +| xs:time | ^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$ | + +For example: + +- To specify numbers: you must use a dot as the decimal separator, and not use a thousands separator (e.g. `4.2` is valid, but `1.234,5` is invalid). Scientific notation is allowed (e.g. `1e3` to represent `1000`). +- To specify boolean: valid values are `true` or `false`, `0`, or `1`. ## Notes diff --git a/ids-lib/ErrorCodes.md b/ids-lib/ErrorCodes.md index 6183f05..f31204d 100644 --- a/ids-lib/ErrorCodes.md +++ b/ids-lib/ErrorCodes.md @@ -27,6 +27,7 @@ | 305 | Invalid Constraint Value | | 306 | Schema validation error | | 307 | Schema validation warning | +| 308 | Invalid facet type for xs:restriction | | 400 | Ifc errors | | 401 | Reserved prefix | | 500 | Application errors | diff --git a/ids-lib/IdsSchema/IdsNodes/Facets/IdsAttribute.cs b/ids-lib/IdsSchema/IdsNodes/Facets/IdsAttribute.cs index 95bd9e5..6dcead2 100644 --- a/ids-lib/IdsSchema/IdsNodes/Facets/IdsAttribute.cs +++ b/ids-lib/IdsSchema/IdsNodes/Facets/IdsAttribute.cs @@ -1,4 +1,5 @@ using IdsLib.IdsSchema.Cardinality; +using IdsLib.IdsSchema.XsNodes; using IdsLib.IfcSchema; using IdsLib.IfcSchema.TypeFilters; using IdsLib.Messages; @@ -78,17 +79,17 @@ internal protected override Audit.Status PerformAudit(AuditStateInformation stat { // if a value is defined then the type must be value type // we can also check that any value constraint matches the expected type - var possibleTypes = schema.GetAttributesTypes(matchingAttributeNames).ToList(); - if (!possibleTypes.Any()) + var possibleAttributeTypes = schema.GetAttributesTypes(matchingAttributeNames).ToList(); + if (!possibleAttributeTypes.Any()) { ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"no valid base type exists", schema); } - else if (possibleTypes.Count == 1) + else if (possibleAttributeTypes.Count == 1) { - var expected = possibleTypes.First(); - if (value.HasXmlBaseType(out var baseType)) + var expected = possibleAttributeTypes.First(); + if (value.HasXmlBaseType(out var baseType)) // this is an xml constraint { - if (!possibleTypes.Contains(baseType)) + if (!possibleAttributeTypes.Contains(baseType)) { if (string.IsNullOrEmpty(baseType)) ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found empty but expected `{expected}`", schema); @@ -96,14 +97,18 @@ internal protected override Audit.Status PerformAudit(AuditStateInformation stat ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found `{baseType}` but expected `{expected}`", schema); } } + else if (value.HasSimpleValue(out var simpleValueString)) + { + ret |= XsTypes.AuditStringValue(logger, XsTypes.GetBaseFrom(expected), simpleValueString, value); + } } else { if (value.HasXmlBaseType(out var baseType)) { - if (!possibleTypes.Contains(baseType)) + if (!possibleAttributeTypes.Contains(baseType)) { - string expected = string.Join(", ", possibleTypes); + string expected = string.Join(", ", possibleAttributeTypes); if (string.IsNullOrEmpty(baseType)) ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found empty but expected a close list ({expected})", schema); else diff --git a/ids-lib/IdsSchema/IdsNodes/Facets/IdsProperty.cs b/ids-lib/IdsSchema/IdsNodes/Facets/IdsProperty.cs index 058b36a..dedae15 100644 --- a/ids-lib/IdsSchema/IdsNodes/Facets/IdsProperty.cs +++ b/ids-lib/IdsSchema/IdsNodes/Facets/IdsProperty.cs @@ -1,4 +1,5 @@ using IdsLib.IdsSchema.Cardinality; +using IdsLib.IdsSchema.XsNodes; using IdsLib.IfcSchema; using IdsLib.IfcSchema.TypeFilters; using IdsLib.Messages; @@ -157,16 +158,24 @@ internal protected override Audit.Status PerformAudit(AuditStateInformation stat { if (SchemaInfo.TryParseIfcDataType(dtMatch, out var fnd, false)) { - if (!string.IsNullOrEmpty(fnd.BackingType) && value is not null && value.HasXmlBaseType(out var baseType)) + if (!string.IsNullOrEmpty(fnd.BackingType) && value is not null) { - if (fnd.BackingType != baseType) + if (value.HasXmlBaseType(out var baseType)) { - if (string.IsNullOrEmpty(baseType)) - ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found empty but expected `{fnd.BackingType}` for `{fnd.IfcDataTypeClassName}`", schema); - else - ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found `{baseType}` but expected `{fnd.BackingType}` for `{fnd.IfcDataTypeClassName}`", schema); + if (fnd.BackingType != baseType) + { + if (string.IsNullOrEmpty(baseType)) + ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found empty but expected `{fnd.BackingType}` for `{fnd.IfcDataTypeClassName}`", schema); + else + ret |= IdsErrorMessages.Report303RestrictionBadType(logger, value, $"found `{baseType}` but expected `{fnd.BackingType}` for `{fnd.IfcDataTypeClassName}`", schema); + } + } + else if (value.HasSimpleValue(out var simpleValueString)) + { + ret |= XsTypes.AuditStringValue(logger, XsTypes.GetBaseFrom(fnd.BackingType!), simpleValueString, value); } - } + + } } } } diff --git a/ids-lib/IdsSchema/IdsXmlNode.cs b/ids-lib/IdsSchema/IdsXmlNode.cs index 96cf0ad..002b22b 100644 --- a/ids-lib/IdsSchema/IdsXmlNode.cs +++ b/ids-lib/IdsSchema/IdsXmlNode.cs @@ -192,4 +192,16 @@ internal bool HasXmlBaseType([NotNullWhen(true)]out string? baseType) baseType = rest.BaseAsString; return true; } + + internal bool HasSimpleValue([NotNullWhen(true)] out string? simpleValueString) + { + var rest = GetChildNode("simpleValue"); + if (rest is null) + { + simpleValueString = null; + return false; + } + simpleValueString = rest.Value; + return true; + } } diff --git a/ids-lib/IdsSchema/XsNodes/XsEnumeration.cs b/ids-lib/IdsSchema/XsNodes/XsEnumeration.cs index b2fb78e..f170347 100644 --- a/ids-lib/IdsSchema/XsNodes/XsEnumeration.cs +++ b/ids-lib/IdsSchema/XsNodes/XsEnumeration.cs @@ -43,8 +43,6 @@ public bool TryMatch(IEnumerable candidateStrings, bool ignoreCase, out return matches.Any(); } - - public string Value => value; protected internal override Audit.Status PerformAudit(AuditStateInformation stateInfo, ILogger? logger) @@ -52,41 +50,7 @@ protected internal override Audit.Status PerformAudit(AuditStateInformation stat Audit.Status ret = Audit.Status.Ok; if (!TryGetUpperNode(logger, this, XsRestriction.RestrictionIdentificationArray, out var restriction, out var retStatus)) return retStatus; - - switch (restriction.Base) - { - case XsTypes.BaseTypes.XsAnyUri: // todo: implement Uri value filter - case XsTypes.BaseTypes.Invalid: // notified in the the restriction already, do nothing here - case XsTypes.BaseTypes.Undefined: // todo: to be discussed in group - case XsTypes.BaseTypes.XsString: // nothing - break; - case XsTypes.BaseTypes.XsBoolean: - if (value != "false" && value != "true") - ret |= IdsErrorMessages.Report305BadConstraintValue(logger, this, value, restriction.Base); - break; - case XsTypes.BaseTypes.XsInteger: - if (!XsTypes.IsValid(value, restriction.Base)) - ret |= IdsErrorMessages.Report305BadConstraintValue(logger, this, value, restriction.Base); - break; - case XsTypes.BaseTypes.XsDouble: - case XsTypes.BaseTypes.XsFloat: - case XsTypes.BaseTypes.XsDecimal: - if (!XsTypes.IsValid(value, restriction.Base)) - ret |= IdsErrorMessages.Report305BadConstraintValue(logger, this, value, restriction.Base); - break; - case XsTypes.BaseTypes.XsDuration: - case XsTypes.BaseTypes.XsDateTime: - case XsTypes.BaseTypes.XsDate: - case XsTypes.BaseTypes.XsTime: - if (!XsTypes.IsValid(value, restriction.Base)) - ret |= IdsErrorMessages.Report305BadConstraintValue(logger, this, value, restriction.Base); - break; - - - default: - ret |= IdsErrorMessages.Report501UnexpectedScenario(logger, $"type evaluation not implemented for `{restriction.Base}`", this); - break; - } - return ret; + ret |= XsTypes.AuditStringValue(logger, restriction.Base, value, this); + return ret; } } diff --git a/ids-lib/IdsSchema/XsNodes/XsRestriction.cs b/ids-lib/IdsSchema/XsNodes/XsRestriction.cs index 2323912..461e86d 100644 --- a/ids-lib/IdsSchema/XsNodes/XsRestriction.cs +++ b/ids-lib/IdsSchema/XsNodes/XsRestriction.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Xml; @@ -91,17 +92,19 @@ public bool TryMatch(IEnumerable candidateStrings, bool ignoreCase, out } // taken from: https://www.w3.org/TR/xmlschema-2/ - private Dictionary> validConstraints = new Dictionary>() + private Dictionary> validConstraintsDictionary = new Dictionary>() { - {XsTypes.BaseTypes.XsString, ["pattern", "enumeration", "whiteSpace", "minLength", "maxLength", "length"] }, - {XsTypes.BaseTypes.XsDouble, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsFloat, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsDuration, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsDateTime, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsTime, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsDate, ["pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - {XsTypes.BaseTypes.XsDecimal, ["totalDigits", "fractionDigits", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, - }; + {XsTypes.BaseTypes.XsString, ["annotation", "pattern", "enumeration", "whiteSpace", "minLength", "maxLength", "length"] }, + {XsTypes.BaseTypes.XsDouble, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsFloat, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsDuration, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsDateTime, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsTime, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsDate, ["annotation", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsDecimal, ["annotation", "totalDigits", "fractionDigits", "pattern", "enumeration", "whiteSpace", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive"] }, + {XsTypes.BaseTypes.XsBoolean, ["annotation", "pattern", "whiteSpace"] }, + {XsTypes.BaseTypes.XsInteger, ["annotation", "totalDigits", "fractionDigits", "pattern", "whiteSpace", "enumeration", "maxInclusive", "maxExclusive", "minInclusive", "minExclusive"] }, + }; protected internal override Audit.Status PerformAudit(AuditStateInformation stateInfo, ILogger? logger) { @@ -112,19 +115,19 @@ protected internal override Audit.Status PerformAudit(AuditStateInformation stat ret |= IdsErrorMessages.Report303RestrictionBadType(logger, this, BaseAsString); else { - if (validConstraints.TryGetValue(Base, out var constraints)) + if (validConstraintsDictionary.TryGetValue(Base, out var validConstraints)) { foreach (var child in Children) { - if (!constraints!.Contains(child.type)) + if (!validConstraints!.Contains(child.type)) { - - } + ret |= IdsErrorMessages.Report308RestrictionInvalidFacet(logger, Base, child, validConstraints); + } } } else { - + Debug.WriteLine($"We need to implement the list of validConstraints for {Base}"); } } diff --git a/ids-lib/IdsSchema/XsNodes/XsTypes.cs b/ids-lib/IdsSchema/XsNodes/XsTypes.cs index 6524535..fb4b75e 100644 --- a/ids-lib/IdsSchema/XsNodes/XsTypes.cs +++ b/ids-lib/IdsSchema/XsNodes/XsTypes.cs @@ -1,38 +1,63 @@ -using System; +using IdsLib.Messages; +using Microsoft.Extensions.Logging; +using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; +using static IdsLib.Audit; namespace IdsLib.IdsSchema.XsNodes { /// /// Utility class for XSD type management /// - public static class XsTypes + public static partial class XsTypes { - private readonly static Regex regexInteger = new(@"^[+-]?(\d+)$", RegexOptions.Compiled); - private readonly static Regex regexFloating = new(@"^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$", RegexOptions.Compiled); - private readonly static Regex regexDuration = new(@"^[-+]?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$", RegexOptions.Compiled); - private readonly static Regex regexDate = new(@"^\d{4}-\d{2}-\d{2}(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); - private readonly static Regex regexTime = new(@"^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); - private readonly static Regex regexDateTime = new(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); + internal static Status AuditStringValue(ILogger? logger, XsTypes.BaseTypes baseType, string value, IdsXmlNode node) + { + var ret = Status.Ok; + switch (baseType) + { + case XsTypes.BaseTypes.XsAnyUri: // todo: implement Uri value filter + case XsTypes.BaseTypes.Invalid: // notified in the the restriction already, do nothing here + case XsTypes.BaseTypes.Undefined: // todo: ensure this is notified somewhere + case XsTypes.BaseTypes.XsString: + break; // nothing to do + case XsTypes.BaseTypes.XsBoolean: + case XsTypes.BaseTypes.XsInteger: + case XsTypes.BaseTypes.XsDouble: + case XsTypes.BaseTypes.XsFloat: + case XsTypes.BaseTypes.XsDecimal: + case XsTypes.BaseTypes.XsDuration: + case XsTypes.BaseTypes.XsDateTime: + case XsTypes.BaseTypes.XsDate: + case XsTypes.BaseTypes.XsTime: + if (!XsTypes.IsValid(value, baseType)) + ret = IdsErrorMessages.Report305BadConstraintValue(logger, node, value, baseType); + break; + default: + ret = IdsErrorMessages.Report501UnexpectedScenario(logger, $"type evaluation not implemented for `{baseType}`", node); + break; + } + return ret; + } /// /// Determines if a string value is compatible with a given type /// /// the string value to parse /// The expected base type - /// TRUE if compatible, false otherwise + /// TRUE if compatible, FALSE otherwise public static bool IsValid(string valueString, BaseTypes @base) - { - switch(@base) + { + switch (@base) { case BaseTypes.XsInteger: return regexInteger.IsMatch(valueString); case BaseTypes.XsDouble: case BaseTypes.XsFloat: case BaseTypes.XsDecimal: - return regexFloating.IsMatch(valueString); + return regexDouble.IsMatch(valueString); case BaseTypes.XsDate: return regexDate.IsMatch(valueString); case BaseTypes.XsTime: @@ -41,6 +66,16 @@ public static bool IsValid(string valueString, BaseTypes @base) return regexDateTime.IsMatch(valueString); case BaseTypes.XsDuration: return regexDuration.IsMatch(valueString); + case BaseTypes.XsString: + return true; + case BaseTypes.Invalid: + return false; + case BaseTypes.Undefined: + return false; + case BaseTypes.XsBoolean: + return regexBoolean.IsMatch(valueString); + case BaseTypes.XsAnyUri: // todo: what is the regex for an URI? + break; } return false; } diff --git a/ids-lib/IdsSchema/XsNodes/XsTypes.g.cs b/ids-lib/IdsSchema/XsNodes/XsTypes.g.cs new file mode 100644 index 0000000..88e7763 --- /dev/null +++ b/ids-lib/IdsSchema/XsNodes/XsTypes.g.cs @@ -0,0 +1,20 @@ +// +// This code was automatically generated. +// Any changes made to this file will be lost. + +using System.Text.RegularExpressions; + +namespace IdsLib.IdsSchema.XsNodes +{ + public static partial class XsTypes + { + private readonly static Regex regexBoolean = new(@"^(true|false|0|1)$", RegexOptions.Compiled); + private readonly static Regex regexDate = new(@"^\d{4}-\d{2}-\d{2}(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); + private readonly static Regex regexDateTime = new(@"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); + private readonly static Regex regexDouble = new(@"^([-+]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?|NaN|\+INF|-INF)$", RegexOptions.Compiled); + private readonly static Regex regexDuration = new(@"^[-+]?P(\d+Y)?(\d+M)?(\d+D)?(T(\d+H)?(\d+M)?(\d+S)?)?$", RegexOptions.Compiled); + private readonly static Regex regexInteger = new(@"^[+-]?(\d+)$", RegexOptions.Compiled); + private readonly static Regex regexString = new(@"^.?$", RegexOptions.Compiled); + private readonly static Regex regexTime = new(@"^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|([+-]\d{2}:\d{2}))?$", RegexOptions.Compiled); + } +} diff --git a/ids-lib/IfcSchema/SchemaInfo.cs b/ids-lib/IfcSchema/SchemaInfo.cs index b2dcfa3..9afb9c0 100644 --- a/ids-lib/IfcSchema/SchemaInfo.cs +++ b/ids-lib/IfcSchema/SchemaInfo.cs @@ -1,8 +1,10 @@ -using System; +using IdsLib.IdsSchema.XsNodes; +using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; using System.Net; @@ -721,5 +723,18 @@ public IEnumerable GetAttributesTypes(IEnumerable attributeNames return possible.Distinct(); } + /// + /// Returns a distinct enumerable of the backing types of the required attributes, given a set of attribut names + /// + /// The names of the attributes to evaluate + /// string names of the types found in the evaluation of the attributes + public IEnumerable GetAttributesXmlTypes(IEnumerable attributeNames) + { + foreach (var stringAttribute in GetAttributesTypes(attributeNames)) + { + yield return XsTypes.GetBaseFrom(stringAttribute); + } + } + } } diff --git a/ids-lib/LibraryInformation.cs b/ids-lib/LibraryInformation.cs index 2139705..f3b59d3 100644 --- a/ids-lib/LibraryInformation.cs +++ b/ids-lib/LibraryInformation.cs @@ -26,5 +26,5 @@ public static class LibraryInformation /// /// Static field with hardcoded DLL version number. /// - public static string AssemblyVersion => "1.0.73"; + public static string AssemblyVersion => "1.0.74"; } diff --git a/ids-lib/Messages/IdsErrorMessages.cs b/ids-lib/Messages/IdsErrorMessages.cs index 0c876ad..f45182f 100644 --- a/ids-lib/Messages/IdsErrorMessages.cs +++ b/ids-lib/Messages/IdsErrorMessages.cs @@ -159,7 +159,7 @@ internal static Audit.Status Report304RestrictionEmptyContent(ILogger? logger, I internal static Audit.Status Report305BadConstraintValue(ILogger? logger, IdsXmlNode context, string value, XsTypes.BaseTypes baseType) { - logger?.LogError("Error {errorCode}: Invalid value '{vers}' for base type '{baseType}' on {location}.", 305, value, baseType, context.GetNodeIdentification()); + logger?.LogError("Error {errorCode}: Invalid value `{invalidStringValue}` for base type `{baseType}` on {location}.", 305, value, baseType, context.GetNodeIdentification()); return Audit.Status.IdsContentError; } @@ -173,7 +173,13 @@ internal static void Report307SchemaComplianceWarning(ILogger? logger, LogLevel logger?.Log(level, "{LogLevel} {errorCode}: Schema compliance warning on {location}; {message}", level, 307, location, message); } - internal static Audit.Status Report401ReservedPrefix(ILogger? logger, IdsXmlNode context, string prefix, string field, SchemaInfo schema, string value) + internal static Audit.Status Report308RestrictionInvalidFacet(ILogger? logger, XsTypes.BaseTypes restrictionBase, IdsXmlNode invalidFacetType, IEnumerable validFacetTypes) + { + logger?.LogError("Error {errorCode}: Invalid type `{invalidFacetType}` for restriction with base `{base}` (valid types are: {validTypes}) on {location}.", 308, invalidFacetType.type, restrictionBase, string.Join(", ", validFacetTypes), invalidFacetType.GetNodeIdentification()); + return Audit.Status.IdsContentError; + } + + internal static Audit.Status Report401ReservedPrefix(ILogger? logger, IdsXmlNode context, string prefix, string field, SchemaInfo schema, string value) { logger?.LogError("Error {errorCode}: Reserved prefix '{prefix}' for {field} ({matcherValue}) in the context of {schema} on {location}.", 401, prefix, field, value, schema.Version, context.GetNodeIdentification()); return Audit.Status.IdsContentError; diff --git a/ids-lib/ids-lib.csproj b/ids-lib/ids-lib.csproj index 56ea1c0..725780b 100644 --- a/ids-lib/ids-lib.csproj +++ b/ids-lib/ids-lib.csproj @@ -20,7 +20,7 @@ First implementation. README.md - 1.0.73 + 1.0.74 $(AssemblyVersion) $(AssemblyVersion) true diff --git a/ids-tool.tests/AuditTests.cs b/ids-tool.tests/AuditTests.cs index b5682b2..a289cfa 100644 --- a/ids-tool.tests/AuditTests.cs +++ b/ids-tool.tests/AuditTests.cs @@ -131,7 +131,7 @@ public static IEnumerable GetInvalidCases() yield return new object[] { "InvalidFiles/InvalidIfcOccurs.ids", 11, Audit.Status.IdsStructureError | Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidEntityNames.ids", 3, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidAttributeNames.ids", 2, Audit.Status.IdsContentError }; - yield return new object[] { "InvalidFiles/InvalidAttributeTypes.ids", 5, Audit.Status.IdsContentError }; + yield return new object[] { "InvalidFiles/InvalidAttributeTypes.ids", 6, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidAttributeCardinality.ids", 3, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidAttributeForClass.ids", 1, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidIfcEntityPattern.ids", 5, Audit.Status.IdsContentError }; @@ -153,7 +153,7 @@ public static IEnumerable GetInvalidCases() yield return new object[] { "InvalidFiles/InvalidIfcEnumerationIntegerValues.ids", 3, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidRestriction.ids", 2, Audit.Status.IdsContentError }; yield return new object[] { "InvalidFiles/InvalidApplicability.ids", 3, Audit.Status.IdsContentError | Audit.Status.IdsStructureError }; - yield return new object[] { "InvalidFiles/Restrictions/InvalidRestrictions.ids", 5, Audit.Status.IdsContentError | Audit.Status.IdsStructureError }; + yield return new object[] { "InvalidFiles/Restrictions/InvalidRestrictions.ids", 6, Audit.Status.IdsContentError | Audit.Status.IdsStructureError }; } [Theory] diff --git a/ids-tool.tests/InvalidFiles/InvalidAttributeTypes.ids b/ids-tool.tests/InvalidFiles/InvalidAttributeTypes.ids index 3f046ce..550c7a7 100644 --- a/ids-tool.tests/InvalidFiles/InvalidAttributeTypes.ids +++ b/ids-tool.tests/InvalidFiles/InvalidAttributeTypes.ids @@ -107,6 +107,16 @@ + + + + UpdateDate + + + + 10 + + diff --git a/ids-tool.tests/IssueTests.cs b/ids-tool.tests/IssueTests.cs index ceef1c7..dcec63e 100644 --- a/ids-tool.tests/IssueTests.cs +++ b/ids-tool.tests/IssueTests.cs @@ -50,7 +50,7 @@ public void Issue25_IfcPropertySetFound() public void Issue_28_EmptyRestriction() { var f = new FileInfo("IssueFiles/Issue 28 - Empty restriction.ids"); - LoggerAndAuditHelpers.FullAudit(f, XunitOutputHelper, IdsLib.Audit.Status.IdsContentError, 1); + LoggerAndAuditHelpers.FullAudit(f, XunitOutputHelper, IdsLib.Audit.Status.IdsContentError, 2); } [Fact] diff --git a/ids-tool/ids-tool.csproj b/ids-tool/ids-tool.csproj index 310849a..39500d1 100644 --- a/ids-tool/ids-tool.csproj +++ b/ids-tool/ids-tool.csproj @@ -16,7 +16,7 @@ icon.png IDS, buildingSmart - 1.0.73 + 1.0.74 $(AssemblyVersion) $(AssemblyVersion) https://github.com/buildingSMART/IDS-Audit-tool.git