diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs index c9fd9c3e..157964c9 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FhirPathBuilder.cs @@ -50,6 +50,8 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv { "ele-1" => new FhirEle1Validator(), "ext-1" => new FhirExt1Validator(), + "txt-1" => new FhirTxt1Validator(), + "txt-2" => new FhirTxt2Validator(), _ => null }; } diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs new file mode 100644 index 00000000..5cc89663 --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Hl7.Fhir.Support; +using Newtonsoft.Json.Linq; +using System.Linq; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation +{ + /// + /// Represents the hand-coded version of the equivalent running invariant "ext-1". + /// + [DataContract] + public class FhirTxt1Validator : InvariantValidator + { + /// + public override string Key => "txt-1"; + + /// + public override OperationOutcome.IssueSeverity? Severity => OperationOutcome.IssueSeverity.Error; + + /// + public override bool BestPractice => false; + + /// + public override string? HumanDescription => "The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, elements (either name or href), images and internally contained style attributes"; + + /// + protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + { + // Original expression: "expression": "htmlChecks()" + + if (input.Value is null) return (false, null); + + switch (input.Value) + { + case string value: + { + var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); + + if (result) + { + return (true, null); + } + else + { + var issues = errors.Select(e => new IssueAssertion(Issue.XSD_VALIDATION_ERROR, e)); + return (false, new ResultReport(ValidationResult.Failure, issues)); + } + } + default: + return (false, new ResultReport(ValidationResult.Failure, + new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, + $"Narrative should be of type string, but is of type ({input.Value.GetType()})"))); + + } + } + + /// + public override JToken ToJson() => new JProperty("FastInvariant-txt1", new JObject()); + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs new file mode 100644 index 00000000..47ccd9aa --- /dev/null +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Newtonsoft.Json.Linq; +using System.Runtime.Serialization; + +namespace Firely.Fhir.Validation +{ + /// + /// Represents the hand-coded version of the equivalent running invariant "ext-1". + /// + [DataContract] + public class FhirTxt2Validator : InvariantValidator + { + /// + public override string Key => "txt-2"; + + /// + public override OperationOutcome.IssueSeverity? Severity => OperationOutcome.IssueSeverity.Error; + + /// + public override bool BestPractice => false; + + /// + public override string? HumanDescription => "The narrative SHALL have some non-whitespace content"; + + /// + protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + { + //Check whether the narrative contains non-whitespace content. + return (!string.IsNullOrWhiteSpace(input.Value.ToString()), null); + } + + /// + public override JToken ToJson() => new JProperty("FastInvariant-txt2", new JObject()); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index a0aba985..35f2a5c9 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit a0aba985582d6ec94e409e0772ea097bda26a4f7 +Subproject commit 35f2a5c9fb830acd6030d8721dbee9255c7928e9 diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs index a0da27df..5d2c434c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/FhirPathBuilderTests.cs @@ -53,7 +53,15 @@ public void HandcodedFPConstraintsTest() new() { Key = "ext-1" - } + }, + new() + { + Key = "txt-1" + }, + new() + { + Key = "txt-2" + }, } }; @@ -62,11 +70,13 @@ public void HandcodedFPConstraintsTest() var result = _sut.Build(nav, ElementConversionMode.Full); var builders = result.Should() - .HaveCount(2).And + .HaveCount(4).And .AllBeAssignableTo().Which; builders.First().Should().BeOfType(); builders.Skip(1).First().Should().BeOfType(); + builders.Skip(2).First().Should().BeOfType(); + builders.Skip(3).First().Should().BeOfType(); } [TestMethod] diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs index 79304178..babea9de 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SimpleValidationsTests.cs @@ -166,6 +166,31 @@ public void ValidateExtensionCardinality() results = patientSchema!.Validate(patient.ToTypedElement(), _fixture.NewValidationContext()); results.IsSuccessful.Should().Be(true, because: "extensions have the correct cardinality"); } + + [Fact] + public void ValidateNarrativeInvariants() + { + var justWhiteSpace = new Narrative + { + Status = Narrative.NarrativeStatus.Additional, + Div = " " + }; + + var invalidHtml = new Narrative + { + Status = Narrative.NarrativeStatus.Additional, + Div = "
" + }; + + var narrativeSchema = _fixture.SchemaResolver.GetSchema("http://hl7.org/fhir/StructureDefinition/Narrative"); + + var results = narrativeSchema!.Validate(justWhiteSpace.ToTypedElement(), _fixture.NewValidationContext()); + results.IsSuccessful.Should().Be(false, "Instance failed constraint txt-2 \"The narrative SHALL have some non-whitespace content\""); + + results = narrativeSchema!.Validate(invalidHtml.ToTypedElement(), _fixture.NewValidationContext()); + results.IsSuccessful.Should().Be(false, "The element 'div' in namespace 'http://www.w3.org/1999/xhtml' has invalid child element 'script' in namespace 'http://www.w3.org/1999/xhtml'. List of possible elements expected: 'p, h1, h2, h3, h4, h5, h6, div, ul, ol, dl, pre, hr, blockquote, address, table, a, br, span, bdo, map, img, tt, i, b, big, small, em, strong, dfn, code, q, samp, kbd, var, cite, abbr, acronym, sub, sup' in namespace 'http://www.w3.org/1999/xhtml'."); + + } } }