From 4c6a318749d164466d2bf31918a3969f239c4ca8 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 13 Nov 2023 07:38:31 +1100 Subject: [PATCH 1/3] Fix bundle resolution rules to conform to the specification in version R4+ --- .../java/org/hl7/fhir/r5/model/Constants.java | 1 + .../fhir/utilities/i18n/I18nConstants.java | 4 ++ .../src/main/resources/Messages.properties | 4 ++ .../hl7/fhir/validation/BaseValidator.java | 59 +++++++++++++++++-- .../instance/InstanceValidator.java | 6 +- .../instance/type/BundleValidator.java | 58 +++++++++++------- pom.xml | 2 +- 7 files changed, 104 insertions(+), 30 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Constants.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Constants.java index 898624628..b92182ddb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Constants.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Constants.java @@ -43,6 +43,7 @@ public class Constants { public final static String VERSION_MM = "5.0"; public final static String DATE = "Thu, Mar 23, 2023 19:59+1100"; public final static String URI_REGEX = "((http|https):\\/\\/([A-Za-z0-9\\\\\\.\\:\\%\\$\\-]*\\/)*?)?(Account|ActivityDefinition|ActorDefinition|AdministrableProductDefinition|AdverseEvent|AllergyIntolerance|Appointment|AppointmentResponse|ArtifactAssessment|AuditEvent|Basic|Binary|BiologicallyDerivedProduct|BiologicallyDerivedProductDispense|BodyStructure|Bundle|CapabilityStatement|CarePlan|CareTeam|ChargeItem|ChargeItemDefinition|Citation|Claim|ClaimResponse|ClinicalImpression|ClinicalUseDefinition|CodeSystem|Communication|CommunicationRequest|CompartmentDefinition|Composition|ConceptMap|Condition|ConditionDefinition|Consent|Contract|Coverage|CoverageEligibilityRequest|CoverageEligibilityResponse|DetectedIssue|Device|DeviceAssociation|DeviceDefinition|DeviceDispense|DeviceMetric|DeviceRequest|DeviceUsage|DiagnosticReport|DocumentReference|Encounter|EncounterHistory|Endpoint|EnrollmentRequest|EnrollmentResponse|EpisodeOfCare|EventDefinition|Evidence|EvidenceReport|EvidenceVariable|ExampleScenario|ExplanationOfBenefit|FamilyMemberHistory|Flag|FormularyItem|GenomicStudy|Goal|GraphDefinition|Group|GuidanceResponse|HealthcareService|ImagingSelection|ImagingStudy|Immunization|ImmunizationEvaluation|ImmunizationRecommendation|ImplementationGuide|Ingredient|InsurancePlan|InventoryItem|InventoryReport|Invoice|Library|Linkage|List|Location|ManufacturedItemDefinition|Measure|MeasureReport|Medication|MedicationAdministration|MedicationDispense|MedicationKnowledge|MedicationRequest|MedicationStatement|MedicinalProductDefinition|MessageDefinition|MessageHeader|MolecularSequence|NamingSystem|NutritionIntake|NutritionOrder|NutritionProduct|Observation|ObservationDefinition|OperationDefinition|OperationOutcome|Organization|OrganizationAffiliation|PackagedProductDefinition|Parameters|Patient|PaymentNotice|PaymentReconciliation|Permission|Person|PlanDefinition|Practitioner|PractitionerRole|Procedure|Provenance|Questionnaire|QuestionnaireResponse|RegulatedAuthorization|RelatedPerson|RequestOrchestration|Requirements|ResearchStudy|ResearchSubject|RiskAssessment|Schedule|SearchParameter|ServiceRequest|Slot|Specimen|SpecimenDefinition|StructureDefinition|StructureMap|Subscription|SubscriptionStatus|SubscriptionTopic|Substance|SubstanceDefinition|SubstanceNucleicAcid|SubstancePolymer|SubstanceProtein|SubstanceReferenceInformation|SubstanceSourceMaterial|SupplyDelivery|SupplyRequest|Task|TerminologyCapabilities|TestPlan|TestReport|TestScript|Transport|ValueSet|VerificationResult|VisionPrescription)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?"; + public final static String URI_REGEX_XVER = "((http|https):\\/\\/([A-Za-z0-9\\\\\\.\\:\\%\\$\\-]*\\/)*?)?($$)\\/[A-Za-z0-9\\-\\.]{1,64}(\\/_history\\/[A-Za-z0-9\\-\\.]{1,64})?"; public static final String NS_FHIR_ROOT = "http://hl7.org/fhir"; public static final String NS_CDA_ROOT = "http://hl7.org/cda/stds/core"; } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 60463d324..f829b2383 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -28,6 +28,7 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL = "Bundle_BUNDLE_Entry_NoProfile_EXPL"; public static final String BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE = "Bundle_BUNDLE_Entry_NoProfile_TYPE"; public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND = "Bundle_BUNDLE_Entry_NotFound"; + public static final String BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT = "BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_DOCUMENT = "Bundle_BUNDLE_Entry_Orphan_DOCUMENT"; public static final String BUNDLE_BUNDLE_ENTRY_ORPHAN_MESSAGE = "Bundle_BUNDLE_Entry_Orphan_MESSAGE"; public static final String BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = "BUNDLE_BUNDLE_ENTRY_REVERSE_R4"; @@ -1022,6 +1023,9 @@ public class I18nConstants { public static final String FHIRPATH_ARITHMETIC_UNIT = "FHIRPATH_ARITHMETIC_UNIT"; public static final String FHIRPATH_ARITHMETIC_PLUS = "FHIRPATH_ARITHMETIC_PLUS"; public static final String FHIRPATH_ARITHMETIC_MINUS = "FHIRPATH_ARITHMETIC_MINUS"; + public static final String BUNDLE_ENTRY_URL_MATCHES_TYPE_ID = "BUNDLE_ENTRY_URL_MATCHES_TYPE_ID"; + public static final String BUNDLE_ENTRY_URL_MATCHES_NO_ID = "BUNDLE_ENTRY_URL_MATCHES_NO_ID"; + public static final String BUNDLE_ENTRY_URL_ABSOLUTE = "BUNDLE_ENTRY_URL_ABSOLUTE"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index bfcd99321..2bf445919 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -12,6 +12,7 @@ Bundle_BUNDLE_Entry_NoProfile_TYPE = No profile found for {0} resource of type ' Bundle_BUNDLE_Entry_NoProfile_EXPL = Specified profile {2} not found for {0} resource of type ''{0}'' Bundle_BUNDLE_Entry_NO_LOGICAL_EXPL = Specified logical model {1} not found for resource ''Binary/{0}'' Bundle_BUNDLE_Entry_NotFound = Can''t find ''{0}'' in the bundle ({1}) +BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT = Can''t find ''{0}'' in the bundle ({1}). Note that there is a resource in the bundle with the same type and id, but it does not match because of the fullUrl based rules around matching relative resources Bundle_BUNDLE_Entry_Orphan_MESSAGE = Entry {0} isn''t reachable by traversing links (forward or backward) from the MessageHeader, so its presence should be reviewed (is it needed to process the message?) Bundle_BUNDLE_Entry_Orphan_DOCUMENT = Entry {0} isn''t reachable by traversing links (forward or backward) from the Composition BUNDLE_BUNDLE_ENTRY_REVERSE_R4 = Entry {0} isn''t reachable by traversing forwards from the Composition. Only Provenance is approved to be used this way (R4 section 3.3.1) @@ -1079,3 +1080,6 @@ FHIRPATH_ARITHMETIC_QTY = Error in date arithmetic: attempt to add a definite qu FHIRPATH_ARITHMETIC_UNIT = Error in date arithmetic: unrecognized time unit {0} FHIRPATH_ARITHMETIC_PLUS = Error in date arithmetic: Unable to add type {0} to {1} FHIRPATH_ARITHMETIC_MINUS = Error in date arithmetic: Unable to subtract type {0} to {1} +BUNDLE_ENTRY_URL_MATCHES_NO_ID = The fullUrl ''{0}'' looks like a RESTful server URL, but the resource has no id +BUNDLE_ENTRY_URL_MATCHES_TYPE_ID = The fullUrl ''{0}'' looks like a RESTful server URL, so it must end with the correct type and id (/{1}/{2}) +BUNDLE_ENTRY_URL_ABSOLUTE = The fullUrl must be an absolute URL (not ''{0}'') diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index be6470a0a..7d69ede7c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -55,6 +55,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.model.StructureDefinition; @@ -68,14 +69,17 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.IValidationContextResourceLoader; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; +import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; import org.hl7.fhir.utilities.FhirPublication; import org.hl7.fhir.utilities.StandardsStatus; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.i18n.I18nConstants; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; import org.hl7.fhir.utilities.validation.ValidationMessage.Source; +import org.hl7.fhir.validation.BaseValidator.ElementMatch; import org.hl7.fhir.validation.cli.utils.ValidationLevel; import org.hl7.fhir.validation.instance.utils.IndexedElement; import org.hl7.fhir.validation.instance.utils.NodeStack; @@ -83,6 +87,25 @@ import org.hl7.fhir.validation.instance.utils.NodeStack; public class BaseValidator implements IValidationContextResourceLoader { + public class ElementMatch { + + private Element element; + private boolean valid; + protected ElementMatch(Element element, boolean valid) { + super(); + this.element = element; + this.valid = valid; + } + public Element getElement() { + return element; + } + public boolean isValid() { + return valid; + } + + } + + public class BooleanHolder { private boolean value = true; @@ -180,6 +203,7 @@ public class BaseValidator implements IValidationContextResourceLoader { this.xverManager = new XVerExtensionManager(context); } this.debug = debug; + urlRegex = Constants.URI_REGEX_XVER.replace("$$", CommaSeparatedStringBuilder.join("|", context.getResourceNames())); } public BaseValidator(BaseValidator parent) { @@ -199,6 +223,7 @@ public class BaseValidator implements IValidationContextResourceLoader { this.warnOnDraftOrExperimental = parent.warnOnDraftOrExperimental; this.statusWarnings = parent.statusWarnings; this.bpWarnings = parent.bpWarnings; + this.urlRegex = parent.urlRegex; } private boolean doingLevel(IssueSeverity error) { @@ -241,6 +266,8 @@ public class BaseValidator implements IValidationContextResourceLoader { */ private Map validationControl = new HashMap<>(); + protected String urlRegex; + /** * Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails * @@ -998,12 +1025,15 @@ public class BaseValidator implements IValidationContextResourceLoader { return null; } - protected Element resolveInBundle(Element bundle, List entries, String ref, String fullUrl, String type, String id) { + protected ElementMatch resolveInBundle(Element bundle, List entries, String ref, String fullUrl, String type, String id) { @SuppressWarnings("unchecked") Map map = (Map) bundle.getUserData("validator.entrymap"); + Map relMap = (Map) bundle.getUserData("validator.entrymapR"); if (map == null) { map = new HashMap<>(); bundle.setUserData("validator.entrymap", map); + relMap = new HashMap<>(); + bundle.setUserData("validator.entrymapR", relMap); for (Element entry : entries) { String fu = entry.getNamedChildValue(FULL_URL); map.put(fu, entry); @@ -1011,14 +1041,25 @@ public class BaseValidator implements IValidationContextResourceLoader { if (resource != null) { String et = resource.getType(); String eid = resource.getNamedChildValue(ID); - map.put(et+"/"+eid, entry); + if (eid != null) { + if (VersionUtilities.isR4Plus(context.getVersion())) { + relMap.put(et+"/"+eid, entry); + } else { + map.put(et+"/"+eid, entry); + } + } } } } if (Utilities.isAbsoluteUrl(ref)) { // if the reference is absolute, then you resolve by fullUrl. No other thinking is required. - return map.get(ref); + Element e = map.get(ref); + if (e == null) { + return null; + } else { + return new ElementMatch(e, true); + } // for (Element entry : entries) { // String fu = entry.getNamedChildValue(FULL_URL); // if (ref.equals(fu)) @@ -1028,9 +1069,10 @@ public class BaseValidator implements IValidationContextResourceLoader { } else { // split into base, type, and id String u = null; - if (fullUrl != null && fullUrl.endsWith(type + "/" + id)) + if (fullUrl != null && fullUrl.matches(urlRegex) && fullUrl.endsWith(type + "/" + id)) { // fullUrl = complex u = fullUrl.substring(0, fullUrl.length() - (type + "/" + id).length()) + ref; + } // u = fullUrl.substring((type+"/"+id).length())+ref; String[] parts = ref.split("\\/"); if (parts.length >= 2) { @@ -1040,7 +1082,14 @@ public class BaseValidator implements IValidationContextResourceLoader { if (res == null) { res = map.get(t+"/"+i); } - return res; + if (res == null && relMap.containsKey(t+"/"+i)) { + res = relMap.get(t+"/"+i); + return new ElementMatch(res, false); + } else if (res == null) { + return null; + } else { + return new ElementMatch(res, true); + } // for (Element entry : entries) { // String fu = entry.getNamedChildValue(FULL_URL); // if (fu != null && fu.equals(u)) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 2989e0a5f..763e22cf0 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -5381,9 +5381,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!Utilities.noString(ref)) { for (Element bundle : bundles) { List entries = bundle.getChildren(ENTRY); - Element tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase()); - if (tgt != null) { - element.setUserData("validator.bundle.resolution", tgt.getNamedChild(RESOURCE)); + ElementMatch tgt = resolveInBundle(bundle, entries, ref, fu, resource.fhirType(), resource.getIdBase()); + if (tgt != null && tgt.isValid()) { + element.setUserData("validator.bundle.resolution", tgt.getElement().getNamedChild(RESOURCE)); return; } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java index be6c06d62..48e9a0d75 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/BundleValidator.java @@ -58,7 +58,7 @@ public class BundleValidator extends BaseValidator { if (entries.size() == 0) { ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, stack.getLiteralPath(), !(type.equals(DOCUMENT) || type.equals(MESSAGE)), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST) && ok; } else { - // Get the first entry, the MessageHeader + // Get the first entry, the MessageHeader or Document Element firstEntry = entries.get(0); // Get the stack of the first entry NodeStack firstStack = stack.push(firstEntry, 1, null, null); @@ -75,16 +75,14 @@ public class BundleValidator extends BaseValidator { ok = handleSpecialCaseForLastUpdated(bundle, errors, stack) && ok; } ok = checkAllInterlinked(errors, entries, stack, bundle, false) && ok; - } - if (type.equals(MESSAGE)) { + } else if (type.equals(MESSAGE)) { Element resource = firstEntry.getNamedChild(RESOURCE); if (rule(errors, NO_RULE_DATE, IssueType.INVALID, firstEntry.line(), firstEntry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), resource != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRSTRESOURCE)) { String id = resource.getNamedChildValue(ID); ok = validateMessage(errors, entries, resource, firstStack.push(resource, -1, null, null), fullUrl, id) && ok; ok = checkAllInterlinked(errors, entries, stack, bundle, true) && ok; } - } - if (type.equals(SEARCHSET)) { + } else if (type.equals(SEARCHSET)) { ok = checkSearchSet(errors, bundle, entries, stack) && ok; } // We do not yet have rules requiring that the id and fullUrl match when dealing with messaging Bundles @@ -101,8 +99,24 @@ public class BundleValidator extends BaseValidator { String fullUrl = entry.getNamedChildValue(FULL_URL); String url = getCanonicalURLForEntry(entry); String id = getIdForEntry(entry); + String rtype = getTypeForEntry(entry); + + if (!Utilities.noString(fullUrl)) { + if (Utilities.isAbsoluteUrl(fullUrl)) { + if (rtype != null && fullUrl.matches(urlRegex)) { + if (rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), id != null, I18nConstants.BUNDLE_ENTRY_URL_MATCHES_NO_ID, fullUrl)) { + ok = rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), fullUrl.endsWith("/"+rtype+"/"+id), I18nConstants.BUNDLE_ENTRY_URL_MATCHES_TYPE_ID, fullUrl, rtype, id) && ok; + } else { + ok = false; + } + } + } else { + ok = false; + rule(errors, "2023-11-13", IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_ENTRY_URL_ABSOLUTE, fullUrl); + } + } if (url != null) { - if (!(!url.equals(fullUrl) || (url.matches(uriRegexForVersion()) && url.endsWith("/" + id))) && !isV3orV2Url(url)) + if (!(!url.equals(fullUrl) || (url.matches(urlRegex) && url.endsWith("/" + id))) && !isV3orV2Url(url)) ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_MISMATCHIDURL, url, fullUrl, id) && ok; ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), stack.addToLiteralPath(ENTRY, PATH_ARG), !url.equals(fullUrl) || serverBase == null || (url.equals(Utilities.pathURL(serverBase, entry.getNamedChild(RESOURCE).fhirType(), id))), I18nConstants.BUNDLE_BUNDLE_ENTRY_CANONICAL, url, fullUrl) && ok; } @@ -111,8 +125,7 @@ public class BundleValidator extends BaseValidator { ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, entry.line(), entry.col(), estack.getLiteralPath(), fullUrlOptional || fullUrl != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_FULLURL_REQUIRED) && ok; } // check bundle profile requests - if (entry.hasChild(RESOURCE)) { - String rtype = entry.getNamedChild(RESOURCE).fhirType(); + if (rtype != null) { int rcount = counter.containsKey(rtype) ? counter.get(rtype)+1 : 0; counter.put(rtype, rcount); Element res = entry.getNamedChild(RESOURCE); @@ -548,9 +561,12 @@ public class BundleValidator extends BaseValidator { } if (ref != null && !Utilities.noString(reference) && !reference.startsWith("#")) { - Element target = resolveInBundle(bundle, entries, reference, fullUrl, type, id); - return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target != null, - I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND, reference, name); + ElementMatch target = resolveInBundle(bundle, entries, reference, fullUrl, type, id); + if (target == null) { + return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), false, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND, reference, name); + } else { + return rule(errors, NO_RULE_DATE, IssueType.INVALID, ref.line(), ref.col(), stack.addToLiteralPath("reference"), target.isValid(), I18nConstants.BUNDLE_BUNDLE_ENTRY_NOTFOUND_APPARENT, reference, name); + } } return true; } @@ -594,9 +610,9 @@ public class BundleValidator extends BaseValidator { for (EntrySummary e : entryList) { Set references = findReferences(e.getEntry()); for (String ref : references) { - Element tgt = resolveInBundle(bundle, entries, ref, e.getEntry().getChildValue(FULL_URL), e.getResource().fhirType(), e.getResource().getIdBase()); - if (tgt != null) { - EntrySummary t = entryForTarget(entryList, tgt); + ElementMatch tgt = resolveInBundle(bundle, entries, ref, e.getEntry().getChildValue(FULL_URL), e.getResource().fhirType(), e.getResource().getIdBase()); + if (tgt != null && tgt.isValid()) { + EntrySummary t = entryForTarget(entryList, tgt.getElement()); if (t != null ) { if (t != e) { // System.out.println("Entry "+e.getIndex()+" refers to "+t.getIndex()+" by ref '"+ref+"'"); @@ -689,13 +705,6 @@ public class BundleValidator extends BaseValidator { return Utilities.existsInList(fhirType, "Provenance"); } - private String uriRegexForVersion() { - if (VersionUtilities.isR3Ver(context.getVersion())) - return URI_REGEX3; - else - return Constants.URI_REGEX; - } - private String getCanonicalURLForEntry(Element entry) { Element e = entry.getNamedChild(RESOURCE); if (e == null) @@ -710,6 +719,13 @@ public class BundleValidator extends BaseValidator { return e.getNamedChildValue(ID); } + private String getTypeForEntry(Element entry) { + Element e = entry.getNamedChild(RESOURCE); + if (e == null) + return null; + return e.fhirType(); + } + /** * Check each resource entry to ensure that the entry's fullURL includes the resource's id * value. Adds an ERROR ValidationMessge to errors List for a given entry if it references diff --git a/pom.xml b/pom.xml index 89bcebf70..95355ae64 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 32.0.1-jre 6.4.1 - 1.4.16 + 1.4.17-SNAPSHOT 2.15.2 5.9.2 1.8.2 From 90b0e0d3d6877d74e5d066809c6393ec22b58ac2 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 13 Nov 2023 07:38:59 +1100 Subject: [PATCH 2/3] Fix issue where markdown with multiple characters was being cut off sometimes --- .../StructureDefinitionRenderer.java | 47 ++++++++++++------- .../hl7/fhir/utilities/xhtml/XhtmlParser.java | 5 ++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index b128c80a5..d14272515 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -91,6 +91,7 @@ import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMo import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; +import org.hl7.fhir.utilities.xhtml.XhtmlNodeList; import org.hl7.fhir.utilities.xhtml.XhtmlParser; public class StructureDefinitionRenderer extends ResourceRenderer { @@ -3381,57 +3382,70 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } public XhtmlNode compareMarkdown(String location, PrimitiveType md, PrimitiveType compare, int mode) throws FHIRException, IOException { + XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div"); if (compare == null || mode == GEN_MODE_DIFF) { if (md.hasValue()) { String xhtml = hostMd.processMarkdown(location, md); if (Utilities.noString(xhtml)) { return null; } - XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); try { - renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml)); + renderStatusDiv(md, ndiv).addChildren(fixFontSizes(new XhtmlParser().parseMDFragment(xhtml), 11)); } catch (Exception e) { - x.span("color: maroon").tx(e.getLocalizedMessage()); + ndiv.span("color: maroon").tx(e.getLocalizedMessage()); + e.printStackTrace(); } - return x; + return ndiv; } else { return null; } } else if (areEqual(compare, md)) { if (md.hasValue()) { - String xhtml = "
"+hostMd.processMarkdown(location, md)+"
"; - XhtmlNode div = new XhtmlParser().parseFragment(xhtml); - for (XhtmlNode n : div.getChildNodes()) { + String xhtml = hostMd.processMarkdown(location, md); + List nodes = new XhtmlParser().parseMDFragment(xhtml); + for (XhtmlNode n : nodes) { if (n.getNodeType() == NodeType.Element) { n.style(unchangedStyle()); } } - return div; + ndiv.addChildren(nodes); + return ndiv; } else { return null; } } else { - XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div"); if (md.hasValue()) { - String xhtml = "
"+hostMd.processMarkdown(location, md)+"
"; - XhtmlNode div = new XhtmlParser().parseFragment(xhtml); - ndiv.copyAllContent(div); + String xhtml = hostMd.processMarkdown(location, md); + List div = new XhtmlParser().parseMDFragment(xhtml); + ndiv.addChildren(div); } if (compare.hasValue()) { String xhtml = "
"+hostMd.processMarkdown(location, compare)+"
"; - XhtmlNode div = new XhtmlParser().parseFragment(xhtml); - for (XhtmlNode n : div.getChildNodes()) { + List div = new XhtmlParser().parseMDFragment(xhtml); + for (XhtmlNode n : div) { if (n.getNodeType() == NodeType.Element) { n.style(removedStyle()); } } ndiv.br(); - ndiv.copyAllContent(div); + ndiv.addChildren(div); } return ndiv; } } + private List fixFontSizes(List nodes, int size) { + for (XhtmlNode x : nodes) { + if (Utilities.existsInList(x.getName(), "p", "li") && !x.hasAttribute("style")) { + x.style("font-size: "+size+"px"); + } + if (x.hasChildren()) { + fixFontSizes(x.getChildNodes(), size); + } + } + return nodes; + } + private boolean areEqual(PrimitiveType compare, PrimitiveType md) { if (compare == null && md == null) { return true; @@ -3934,7 +3948,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer { list.merge(new DiscriminatorWithStatus(d)); } } - x.tx(", and can be differentiated using the following discriminators: "); var ul = x.ul(); for (DiscriminatorWithStatus rc : list) { rc.render(x.li()); @@ -4316,7 +4329,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement()); if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) { bindingDesc = new XhtmlNode(NodeType.Element, "div"); - bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding))); + bindingDesc.addChildren(new XhtmlParser().parseMDFragment(hostMd.processMarkdown("Binding.description", newBinding))); } else { StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java index a54d3b173..9ac5f47ad 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java @@ -1285,6 +1285,11 @@ public class XhtmlParser { } } + public List parseMDFragment(String source) throws IOException, FHIRException { + XhtmlNode div = parseFragment( "
"+source+"
"); + return div.getChildNodes(); + } + public XhtmlNode parseFragment(String source) throws IOException, FHIRException { rdr = new StringReader(source); try { From e56dfb86931ab273a423bc45ba1d2b21180a2ce5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 13 Nov 2023 08:10:56 +1100 Subject: [PATCH 3/3] Add support for NZ IPS --- .../java/org/hl7/fhir/validation/ValidatorCli.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index 73903173b..17bdeebf2 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -301,6 +301,19 @@ public class ValidatorCli { res.add("-bundle"); res.add("Composition:0"); res.add("http://hl7.org.au/fhir/ips/StructureDefinition/Composition-au-ips"); + } else if (a.equals("-ips:nz")) { + res.add("-version"); + res.add("4.0"); + res.add("-check-ips-codes"); + res.add("-ig"); + res.add("tewhatuora.fhir.nzps#current"); + res.add("-profile"); + res.add("https://standards.digital.health.nz/fhir/StructureDefinition/nzps-bundle"); + res.add("-extension"); + res.add("any"); + res.add("-bundle"); + res.add("Composition:0"); + res.add("https://standards.digital.health.nz/fhir/StructureDefinition/nzps-composition"); } else if (a.equals("-ips#")) { res.add("-version"); res.add("4.0");