From 2bd73c650ee7ea6458687d662a680b21db0b65e9 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 27 Feb 2023 07:30:04 +1100 Subject: [PATCH] Work on StructureMap validation --- .../loaders/loaderR5/BaseLoaderR5.java | 100 ++++++++++- .../loaders/loaderR5/R2016MayToR5Loader.java | 36 ++-- .../loaders/loaderR5/R2ToR5Loader.java | 32 +--- .../loaders/loaderR5/R3ToR5Loader.java | 38 +--- .../loaders/loaderR5/R4BToR5Loader.java | 36 +--- .../loaders/loaderR5/R4ToR5Loader.java | 35 +--- .../loaders/loaderR5/R5ToR5Loader.java | 36 +--- .../fhir/r4/utils/StructureMapUtilities.java | 6 +- .../hl7/fhir/r5/context/ContextUtilities.java | 14 ++ .../hl7/fhir/r5/context/IWorkerContext.java | 18 +- .../fhir/r5/context/SimpleWorkerContext.java | 2 +- .../hl7/fhir/r5/elementmodel/FmlParser.java | 27 ++- .../fhir/r5/test/utils/TestPackageLoader.java | 10 ++ .../java/org/hl7/fhir/r5/utils/FHIRLexer.java | 29 ++- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 3 + .../structuremap/StructureMapUtilities.java | 46 +++-- .../hl7/fhir/utilities/VersionUtilities.java | 14 +- .../fhir/utilities/i18n/I18nConstants.java | 3 +- .../src/main/resources/Messages.properties | 16 +- .../org/hl7/fhir/validation/IgLoader.java | 22 ++- .../hl7/fhir/validation/ValidationEngine.java | 18 +- .../services/StandAloneValidatorFetcher.java | 2 + .../hl7/fhir/validation/cli/utils/Params.java | 24 ++- .../instance/type/StructureMapValidator.java | 168 ++++++++++++++---- 24 files changed, 476 insertions(+), 259 deletions(-) diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java index b648289f5..3c6fa79ef 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/BaseLoaderR5.java @@ -3,8 +3,21 @@ package org.hl7.fhir.convertors.loaders.loaderR5; import java.io.IOException; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; import org.hl7.fhir.r5.context.IWorkerContext.IContextResourceLoader; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CanonicalType; +import org.hl7.fhir.r5.model.CapabilityStatement; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.OperationDefinition; +import org.hl7.fhir.r5.model.OperationDefinition.OperationDefinitionParameterComponent; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.UriType; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.npm.NpmPackage; @@ -18,12 +31,8 @@ import lombok.experimental.Accessors; public abstract class BaseLoaderR5 implements IContextResourceLoader { protected final String URL_BASE = "http://hl7.org/fhir/"; - protected final String URL_DSTU2 = "http://hl7.org/fhir/1.0/"; - protected final String URL_DSTU2016MAY = "http://hl7.org/fhir/1.4/"; - protected final String URL_DSTU3 = "http://hl7.org/fhir/3.0/"; - protected final String URL_R4 = "http://hl7.org/fhir/4.0/"; protected final String URL_ELEMENT_DEF_NAMESPACE = "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"; - @Getter @Setter protected boolean patchUrls; + protected boolean patchUrls; @Getter @Setter protected boolean killPrimitives; @Getter protected String[] types; protected ILoaderKnowledgeProviderR5 lkp; @@ -73,4 +82,83 @@ public abstract class BaseLoaderR5 implements IContextResourceLoader { } } -} \ No newline at end of file + public boolean isPatchUrls() { + return patchUrls; + } + + public void setPatchUrls(boolean patchUrls) { + this.patchUrls = patchUrls; + } + + protected abstract String versionString(); + + + @Override + public String patchUrl(String url, String type) { + if (!patchUrls || url == null) { + return url; + } else if (url.startsWith("http://hl7.org/fhir/"+type+"/")) { + return "http://hl7.org/fhir/"+versionString()+"/"+url.substring(20); + } else if ("CodeSystem".equals(type) && url.startsWith("http://hl7.org/fhir/")) { + return "http://hl7.org/fhir/"+versionString()+"/"+url.substring(20); + } else { + return url; + } + } + + // we don't patch everything. It's quite hard work to do that, + // and we only patch URLs to support version transforms + // so we just patch sd/od -> vs -> cs + protected void doPatchUrls(Resource resource) { + if (resource instanceof CanonicalResource) { + CanonicalResource cr = (CanonicalResource) resource; + cr.setUrl(patchUrl(cr.getUrl(), cr.fhirType())); + if (cr instanceof StructureDefinition) { + StructureDefinition sd = (StructureDefinition) cr; + new ProfileUtilities(null, null, null, null).setIds(sd, false); + sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); + for (ElementDefinition ed : sd.getSnapshot().getElement()) + patchUrl(ed); + for (ElementDefinition ed : sd.getDifferential().getElement()) + patchUrl(ed); + } + + if (cr instanceof ValueSet) { + ValueSet vs = (ValueSet) cr; + for (ConceptSetComponent inc : vs.getCompose().getInclude()) { + inc.setSystem(patchUrl(inc.getSystem(), "CodeSystem")); + } + for (ConceptSetComponent inc : vs.getCompose().getExclude()) { + inc.setSystem(patchUrl(inc.getSystem(), "CodeSystem")); + } + } + if (cr instanceof OperationDefinition) { + OperationDefinition od = (OperationDefinition) cr; + for (OperationDefinitionParameterComponent param : od.getParameter()) { + patchUrls(param); + } + } + } + } + + private void patchUrls(OperationDefinitionParameterComponent param) { + if (param.hasBinding()) { + param.getBinding().setValueSet(patchUrl(param.getBinding().getValueSet(), "ValueSet")); + } + for (OperationDefinitionParameterComponent p : param.getPart()) { + patchUrls(p); + } + } + + private void patchUrl(ElementDefinition ed) { + for (TypeRefComponent tr : ed.getType()) { + for (CanonicalType s : tr.getTargetProfile()) { + s.setValue(patchUrl(s.getValue(), "StructureDefinitino")); + } + } + if (ed.hasBinding()) { + ed.getBinding().setValueSet(patchUrl(ed.getBinding().getValueSet(), "ValueSet")); + } + } + +} diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2016MayToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2016MayToR5Loader.java index fd1d5f576..5fafc6e38 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2016MayToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2016MayToR5Loader.java @@ -98,13 +98,10 @@ public class R2016MayToR5Loader extends BaseLoaderR5 { } b.getEntry().removeAll(remove); } - for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - new ProfileUtilities(null, null, null, null).setIds(sd, false); - if (patchUrls) { - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_DSTU2016MAY)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); + if (patchUrls) { + for (BundleEntryComponent be : b.getEntry()) { + if (be.hasResource()) { + doPatchUrls(be.getResource()); } } } @@ -128,33 +125,20 @@ public class R2016MayToR5Loader extends BaseLoaderR5 { throw new FHIRException("Cannot kill primitives when using deferred loading"); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU2016MAY)); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU2016MAY)); - } - } - } @Override public List getCodeSystems() { return new ArrayList<>(); } + @Override + protected String versionString() { + return "4.3"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2ToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2ToR5Loader.java index de1bfa1bd..419b97c66 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2ToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R2ToR5Loader.java @@ -102,10 +102,8 @@ public class R2ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader } if (patchUrls) { for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_DSTU2)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); + if (be.hasResource()) { + doPatchUrls(be.getResource()); } } } @@ -126,30 +124,11 @@ public class R2ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader throw new FHIRException("Cannot kill primitives when using deferred loading"); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU2)); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU2)); - } - } - } - @Override public List getCodeSystems() { List list = new ArrayList<>(); @@ -160,4 +139,9 @@ public class R2ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader return list; } + @Override + protected String versionString() { + return "1.0"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R3ToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R3ToR5Loader.java index 58723f2ac..207a309ad 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R3ToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R3ToR5Loader.java @@ -99,15 +99,9 @@ public class R3ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader } if (patchUrls) { for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_DSTU3)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + if (be.hasResource()) { + doPatchUrls(be.getResource()); + } } } return b; @@ -130,33 +124,19 @@ public class R3ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader throw new FHIRException("Cannot kill primitives when using deferred loading"); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU3)); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_DSTU3)); - } - } - } - @Override public List getCodeSystems() { return new ArrayList<>(); } + @Override + protected String versionString() { + return "3.0"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4BToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4BToR5Loader.java index d9007082b..e15d59f74 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4BToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4BToR5Loader.java @@ -103,14 +103,8 @@ public class R4BToR5Loader extends BaseLoaderR5 implements IContextResourceLoade } if (patchUrls) { for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); + if (be.hasResource()) { + doPatchUrls(be.getResource()); } } } @@ -137,34 +131,20 @@ public class R4BToR5Loader extends BaseLoaderR5 implements IContextResourceLoade r5 = new StructureDefinitionHacker(version).fixSD((StructureDefinition) r5); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType("http://hl7.org/fhir")); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - } - } - } @Override public List getCodeSystems() { return new ArrayList<>(); } + @Override + protected String versionString() { + return "4.3"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4ToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4ToR5Loader.java index 310a9d218..1b4fdecc7 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4ToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R4ToR5Loader.java @@ -103,14 +103,8 @@ public class R4ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader } if (patchUrls) { for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); + if (be.hasResource()) { + doPatchUrls(be.getResource()); } } } @@ -137,34 +131,21 @@ public class R4ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader r5 = new StructureDefinitionHacker(version).fixSD((StructureDefinition) r5); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType("http://hl7.org/fhir")); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, "http://hl7.org/fhir/4.0/")); - } - } - } @Override public List getCodeSystems() { return new ArrayList<>(); } + @Override + protected String versionString() { + return "4.0"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R5ToR5Loader.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R5ToR5Loader.java index 3a5494efa..a8197be8f 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R5ToR5Loader.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/loaders/loaderR5/R5ToR5Loader.java @@ -96,14 +96,8 @@ public class R5ToR5Loader extends BaseLoaderR5 { } if (patchUrls) { for (BundleEntryComponent be : b.getEntry()) { - if (be.hasResource() && be.getResource() instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) be.getResource(); - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); + if (be.hasResource()) { + doPatchUrls(be.getResource()); } } } @@ -126,34 +120,20 @@ public class R5ToR5Loader extends BaseLoaderR5 { throw new FHIRException("Cannot kill primitives when using deferred loading"); } if (patchUrls) { - if (r5 instanceof StructureDefinition) { - StructureDefinition sd = (StructureDefinition) r5; - sd.setUrl(sd.getUrl().replace(URL_BASE, URL_R4)); - sd.addExtension().setUrl(URL_ELEMENT_DEF_NAMESPACE).setValue(new UriType(URL_BASE)); - for (ElementDefinition ed : sd.getSnapshot().getElement()) - patchUrl(ed); - for (ElementDefinition ed : sd.getDifferential().getElement()) - patchUrl(ed); - } + doPatchUrls(r5); } return r5; } - - private void patchUrl(ElementDefinition ed) { - for (TypeRefComponent tr : ed.getType()) { - for (CanonicalType s : tr.getTargetProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_R4)); - } - for (CanonicalType s : tr.getProfile()) { - s.setValue(s.getValue().replace(URL_BASE, URL_R4)); - } - } - } @Override public List getCodeSystems() { return new ArrayList<>(); } + @Override + protected String versionString() { + return "5.0"; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java index 6914e851f..d5c78a27a 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/StructureMapUtilities.java @@ -265,8 +265,10 @@ public class StructureMapUtilities { public StructureMapUtilities(IWorkerContext worker) { super(); this.worker = worker; - fpe = new FHIRPathEngine(worker); - fpe.setHostServices(new FFHIRPathHostServices()); + if (worker != null) { + fpe = new FHIRPathEngine(worker); + fpe.setHostServices(new FFHIRPathHostServices()); + } } public static String render(StructureMap map) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java index b5e16816f..69f5d533e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/ContextUtilities.java @@ -23,6 +23,7 @@ import org.hl7.fhir.r5.model.NamingSystem.NamingSystemIdentifierType; import org.hl7.fhir.r5.model.NamingSystem.NamingSystemUniqueIdComponent; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r5.model.StructureMap; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.model.Identifier; @@ -390,5 +391,18 @@ public class ContextUtilities implements ProfileKnowledgeProvider { return concreteResourceNames; } + public List listMaps(String url) { + List res = new ArrayList<>(); + String start = url.substring(0, url.indexOf("*")); + String end = url.substring(url.indexOf("*")+1); + for (StructureMap map : context.fetchResourcesByType(StructureMap.class)) { + String u = map.getUrl(); + if (u.startsWith(start) && u.endsWith(end)) { + res.add(map); + } + } + return res; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java index 39c0dbca6..cd26fab61 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/IWorkerContext.java @@ -320,7 +320,23 @@ public interface IWorkerContext { * * @return */ - List getCodeSystems(); + List getCodeSystems(); + + /** + * if this is true, then the loader will patch canonical URLs and cross-links + * to add /X.X/ into the URL so that different versions can be loaded safely + * + * default is false + */ + void setPatchUrls(boolean value); + + /** + * patch the URL if necessary + * + * @param url + * @return + */ + String patchUrl(String url, String resourceType); } /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java index 493922ce4..bc037ac68 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/SimpleWorkerContext.java @@ -97,7 +97,7 @@ public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerCon private final IContextResourceLoader loader; public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) { - super(pri.getResourceType(), pri.getId(), pri.getUrl(),pri.getVersion()); + super(pri.getResourceType(), pri.getId(), loader == null ? pri.getUrl() :loader.patchUrl(pri.getUrl(), pri.getResourceType()), pri.getVersion()); this.filename = pri.getFilename(); this.loader = loader; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java index 9257bd7b6..11da04160 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/FmlParser.java @@ -55,18 +55,29 @@ public class FmlParser extends ParserBase { } public Element parse(String text) throws FHIRException { - FHIRLexer lexer = new FHIRLexer(text, "source"); + FHIRLexer lexer = new FHIRLexer(text, "source", true); if (lexer.done()) throw lexer.error("Map Input cannot be empty"); - lexer.token("map"); Element result = Manager.build(context, context.fetchTypeDefinition("StructureMap")); try { - result.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url")); - lexer.token("="); - result.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("name")); - if (lexer.hasComments()) { - result.makeElement("description").markLocation(lexer.getCurrentLocation()).setValue(lexer.getAllComments()); + if (lexer.hasToken("map")) { + lexer.token("map"); + result.makeElement("url").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("url")); + lexer.token("="); + result.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.readConstant("name")); + if (lexer.hasComments()) { + result.makeElement("description").markLocation(lexer.getCurrentLocation()).setValue(lexer.getAllComments()); + } + } else { + while (lexer.hasToken("///")) { + lexer.next(); + String fid = lexer.takeDottedToken(); + Element e = result.makeElement(fid).markLocation(lexer.getCurrentLocation()); + lexer.token("="); + e.setValue(lexer.readConstant("meta value")); + } } + lexer.setMetadataFormat(false); while (lexer.hasToken("conceptmap")) parseConceptMap(result, lexer); @@ -366,7 +377,7 @@ public class FmlParser extends ParserBase { private void parseRuleReference(Element rule, FHIRLexer lexer) throws FHIRLexerException { Element ref = rule.addElement("dependent"); - rule.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take()); + ref.makeElement("name").markLocation(lexer.getCurrentLocation()).setValue(lexer.take()); lexer.token("("); boolean done = false; while (!done) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java index 3ee6e683f..4dc369a1f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/test/utils/TestPackageLoader.java @@ -52,4 +52,14 @@ public class TestPackageLoader implements IContextResourceLoader { return new ArrayList<>(); } + @Override + public void setPatchUrls(boolean value) { + + } + + @Override + public String patchUrl(String url, String resourceType) { + return url; + } + } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java index d2387f0f7..ad61f775d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRLexer.java @@ -89,6 +89,7 @@ public class FHIRLexer { private String name; private boolean liquidMode; // in liquid mode, || terminates the expression and hands the parser back to the host private SourceLocation commentLocation; + private boolean metadataFormat; public FHIRLexer(String source, String name) throws FHIRLexerException { this.source = source == null ? "" : source; @@ -102,6 +103,13 @@ public class FHIRLexer { currentLocation = new SourceLocation(1, 1); next(); } + public FHIRLexer(String source, String name, boolean metadataFormat) throws FHIRLexerException { + this.source = source == null ? "" : source; + this.name = name == null ? "??" : name; + this.metadataFormat = metadataFormat; + currentLocation = new SourceLocation(1, 1); + next(); + } public String getCurrent() { return current; } @@ -211,10 +219,13 @@ public class FHIRLexer { } else if (ch == '/') { cursor++; if (cursor < source.length() && (source.charAt(cursor) == '/')) { - // this is en error - should already have been skipped - error("This shouldn't happen?"); + // we've run into metadata + cursor++; + cursor++; + current = source.substring(currentStart, cursor); + } else { + current = source.substring(currentStart, cursor); } - current = source.substring(currentStart, cursor); } else if (ch == '$') { cursor++; while (cursor < source.length() && (source.charAt(cursor) >= 'a' && source.charAt(cursor) <= 'z')) @@ -309,7 +320,7 @@ public class FHIRLexer { boolean last13 = false; boolean done = false; while (cursor < source.length() && !done) { - if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2))) { + if (cursor < source.length() -1 && "//".equals(source.substring(cursor, cursor+2)) && !isMetadataStart()) { commentLocation = currentLocation; int start = cursor+2; while (cursor < source.length() && !((source.charAt(cursor) == '\r') || source.charAt(cursor) == '\n')) { @@ -338,6 +349,10 @@ public class FHIRLexer { } } + private boolean isMetadataStart() { + return metadataFormat && cursor < source.length() - 2 && "///".equals(source.substring(cursor, cursor+3)); + } + private boolean isDateChar(char ch,int start) { int eot = source.charAt(start+1) == 'T' ? 10 : 20; @@ -550,5 +565,11 @@ public class FHIRLexer { public SourceLocation getCommentLocation() { return this.commentLocation; } + public boolean isMetadataFormat() { + return metadataFormat; + } + public void setMetadataFormat(boolean metadataFormat) { + this.metadataFormat = metadataFormat; + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 09980f564..4d4236b25 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -6352,5 +6352,8 @@ public class FHIRPathEngine { this.liquidMode = liquidMode; } + public ProfileUtilities getProfileUtilities() { + return profileUtilities; + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java index b162cb73d..d2ec339af 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/structuremap/StructureMapUtilities.java @@ -619,16 +619,38 @@ public class StructureMapUtilities { } public StructureMap parse(String text, String srcName) throws FHIRException { - FHIRLexer lexer = new FHIRLexer(text, srcName); + FHIRLexer lexer = new FHIRLexer(Utilities.stripBOM(text), srcName, true); if (lexer.done()) throw lexer.error("Map Input cannot be empty"); - lexer.token("map"); StructureMap result = new StructureMap(); - result.setUrl(lexer.readConstant("url")); - lexer.token("="); - result.setName(lexer.readConstant("name")); - result.setDescription(lexer.getAllComments()); - result.setStatus(PublicationStatus.DRAFT); + if (lexer.hasToken("map")) { + lexer.token("map"); + result.setUrl(lexer.readConstant("url")); + lexer.token("="); + result.setName(lexer.readConstant("name")); + result.setDescription(lexer.getAllComments()); + result.setStatus(PublicationStatus.DRAFT); + } else { + while (lexer.hasToken("///")) { + lexer.next(); + String fid = lexer.takeDottedToken(); + lexer.token("="); + switch (fid) { + case "url" : + result.setUrl(lexer.readConstant("url")); + break; + case "name" : + result.setName(lexer.readConstant("name")); + break; + case "title" : + result.setTitle(lexer.readConstant("title")); + break; + default: + lexer.readConstant("nothing"); + // nothing + } + } + } while (lexer.hasToken("conceptmap")) parseConceptMap(result, lexer); @@ -978,11 +1000,11 @@ public class StructureMapUtilities { // type and cardinality lexer.token(":"); source.setType(lexer.takeDottedToken()); - if (!lexer.hasToken("as", "first", "last", "not_first", "not_last", "only_one", "default")) { - source.setMin(lexer.takeInt()); - lexer.token(".."); - source.setMax(lexer.take()); - } + } + if (Utilities.isInteger(lexer.getCurrent())) { + source.setMin(lexer.takeInt()); + lexer.token(".."); + source.setMax(lexer.take()); } if (lexer.hasToken("default")) { lexer.token("default"); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index e1116d608..c61ffcb98 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -214,9 +214,17 @@ public class VersionUtilities { } else if (Utilities.charCount(version, '.') == 2) { String[] p = version.split("\\."); return p[0]+"."+p[1]; - } else { - return null; - } + } else if (Utilities.existsInList(version, "R2", "R2B", "R3", "R4", "R4B", "R5")) { + switch (version) { + case "R2": return "1.0"; + case "R2B": return "1.4"; + case "R3": return "3.0"; + case "R4": return "4.0"; + case "R4B": return "4.3"; + case "R5": return "5.0"; + } + } + return null; } public static String getPatch(String version) { 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 638c9a2a8..2b6f8df69 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 @@ -775,7 +775,7 @@ public class I18nConstants { public static final String ILLEGAL_COMMENT_TYPE = "ILLEGAL_COMMENT_TYPE"; public static final String SD_NO_SLICING_ON_ROOT = "SD_NO_SLICING_ON_ROOT"; public static final String REFERENCE_REF_QUERY_INVALID = "REFERENCE_REF_QUERY_INVALID"; - public static final String SM_EXTENDS_NOT_SUPPORTED = ""; + public static final String SM_RULEGROUP_NOT_FOUND = "SM_RULEGROUP_NOT_FOUND"; public static final String SM_NAME_INVALID = "SM_NAME_INVALID"; public static final String SM_GROUP_INPUT_DUPLICATE = "SM_GROUP_INPUT_DUPLICATE"; public static final String SM_GROUP_INPUT_MODE_INVALID = "SM_GROUP_INPUT_MODE_INVALID"; @@ -803,6 +803,7 @@ public class I18nConstants { public static final String SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = "SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE"; public static final String SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = "SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE"; public static final String SM_TARGET_TRANSFORM_EXPRESSION_ERROR = "SM_TARGET_TRANSFORM_EXPRESSION_ERROR"; + public static final String SM_IMPORT_NOT_FOUND = "SM_IMPORT_NOT_FOUND"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 91058a8eb..41d8f22da 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -825,13 +825,13 @@ EXT_VER_URL_REVERSION = The extension URL must not contain a version. The extens ILLEGAL_COMMENT_TYPE = The fhir_comments property must be an array of strings SD_NO_SLICING_ON_ROOT = Slicing is not allowed at the root of a profile REFERENCE_REF_QUERY_INVALID = The query part of the conditional reference is not a valid query string ({0}) -SM_EXTENDS_NOT_SUPPORTED = Group.extends is not supported +SM_RULEGROUP_NOT_FOUND = The group {0} could not be resolved SM_NAME_INVALID = The name {0} is not valid SM_GROUP_INPUT_DUPLICATE = The name {0} is already used -SM_GROUP_INPUT_MODE_INVALID = The group parameter {0} mode {1} isn't valid +SM_GROUP_INPUT_MODE_INVALID = The group parameter {0} mode {1} isn''t valid SM_GROUP_INPUT_NO_TYPE = The group parameter {0} has no type, so the paths cannot be validated SM_GROUP_INPUT_TYPE_NOT_DECLARED = The type {0} was not declared and is unknown -SM_GROUP_INPUT_MODE_MISMATCH = The type {0} has mode {1} which doesn't match the structure definition {2} +SM_GROUP_INPUT_MODE_MISMATCH = The type {0} has mode {1} which doesn''t match the structure definition {2} SM_GROUP_INPUT_TYPE_UNKNOWN = The type {0} which maps to the canonical URL {1} is not known, so the paths cannot be validated SM_SOURCE_CONTEXT_UNKNOWN = The source context {0} is not known at this point SM_SOURCE_PATH_INVALID = The source path {0}.{1} refers to the path {2} which is unknown @@ -842,17 +842,17 @@ SM_TARGET_CONTEXT_UNKNOWN = The target context {0} is not known at this point SM_TARGET_PATH_INVALID = The target path {0}.{1} refers to the path {2} which is unknown SM_NO_LIST_MODE_NEEDED = A list mode should not be provided since this is a rule that can only be executed once SM_NO_LIST_RULE_ID_NEEDED = A list ruleId should not be provided since this is a rule that can only be executed once -SM_LIST_RULE_ID_ONLY_WHEN_SHARE = A ruleId should only be provided when the rule mode is 'share' -SM_RULE_SOURCE_UNASSIGNED = The source statement doesn't assign a variable to the source - check that this is what is intended +SM_LIST_RULE_ID_ONLY_WHEN_SHARE = A ruleId should only be provided when the rule mode is ''share'' +SM_RULE_SOURCE_UNASSIGNED = The source statement doesn''t assign a variable to the source - check that this is what is intended SM_TARGET_PATH_MULTIPLE_MATCHES = The target path {0}.{1} refers to the path {2} which is could be a reference to multiple elements ({3}). No further checking can be performed -SM_SOURCE_TYPE_INVALID = The type {0} is not valid in this context {1}. The possible types are [{2}] +SM_SOURCE_TYPE_INVALID = The type {0} is not valid in this source context {1}. The possible types are [{2}] SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE = Transform {0} takes {1}-{2} parameter(s) but {3} were found SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE = Transform {0} takes {1} parameter(s) but {2} were found SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked yet -SM_TARGET_NO_TRANSFORM_NO_CHECKED = When there is no transform, parameters can't be provided +SM_TARGET_NO_TRANSFORM_NO_CHECKED = When there is no transform, parameters can''t be provided SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = The value of the type parameter could not be processed SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = The parameter at index {0} could not be processed (type = {1}) SM_TARGET_TRANSFORM_EXPRESSION_ERROR = The FHIRPath expression passed as the evaluate parameter is invalid: {0} - + SM_IMPORT_NOT_FOUND = No maps were found to match {0} - validation may be wrong \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index cb19550ee..31ae9d912 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -20,6 +20,7 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_43_50; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.context.IWorkerContext.IContextResourceLoader; import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.elementmodel.Manager; import org.hl7.fhir.r5.formats.JsonParser; @@ -112,7 +113,9 @@ public class IgLoader { if (!srcPackage.contains("#")) { System.out.print("#" + npm.version()); } - int count = getContext().loadFromPackage(npm, ValidatorUtils.loaderForVersion(npm.fhirVersion())); + IContextResourceLoader loader = ValidatorUtils.loaderForVersion(npm.fhirVersion()); + loader.setPatchUrls(VersionUtilities.isCorePackage(npm.id())); + int count = getContext().loadFromPackage(npm, loader); System.out.println(" - " + count + " resources (" + getContext().clock().milestone() + ")"); } else { System.out.print(" Load " + srcPackage); @@ -183,8 +186,10 @@ public class IgLoader { res.cntType = Manager.FhirFormat.TURTLE; else if (t.getKey().endsWith(".shc")) res.cntType = Manager.FhirFormat.SHC; - else if (t.getKey().endsWith(".txt") || t.getKey().endsWith(".map")) + else if (t.getKey().endsWith(".txt")) res.cntType = Manager.FhirFormat.TEXT; + else if (t.getKey().endsWith(".fml") || t.getKey().endsWith(".map")) + res.cntType = Manager.FhirFormat.FML; else throw new FHIRException("Todo: Determining resource type is not yet done"); } @@ -762,6 +767,7 @@ public class IgLoader { if (isDebug() || ((e.getMessage() != null && e.getMessage().contains("cannot be cast")))) { e.printStackTrace(); } + e.printStackTrace(); } return r; } @@ -774,7 +780,7 @@ public class IgLoader { res = new org.hl7.fhir.dstu3.formats.XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.dstu3.formats.JsonParser().parse(new ByteArrayInputStream(content)); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) res = new org.hl7.fhir.dstu3.utils.StructureMapUtilities(null).parse(new String(content)); else throw new FHIRException("Unsupported format for " + fn); @@ -785,7 +791,7 @@ public class IgLoader { res = new org.hl7.fhir.r4.formats.XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.r4.formats.JsonParser().parse(new ByteArrayInputStream(content)); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) res = new org.hl7.fhir.r4.utils.StructureMapUtilities(null).parse(new String(content), fn); else throw new FHIRException("Unsupported format for " + fn); @@ -796,7 +802,7 @@ public class IgLoader { res = new org.hl7.fhir.r4b.formats.XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) res = new org.hl7.fhir.r4b.formats.JsonParser().parse(new ByteArrayInputStream(content)); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) res = new org.hl7.fhir.r4b.utils.structuremap.StructureMapUtilities(null).parse(new String(content), fn); else throw new FHIRException("Unsupported format for " + fn); @@ -819,15 +825,15 @@ public class IgLoader { else throw new FHIRException("Unsupported format for " + fn); r = VersionConvertorFactory_10_50.convertResource(res, new org.hl7.fhir.convertors.misc.IGR2ConvertorAdvisor5()); - } else if (fhirVersion.startsWith("5.0")) { + } else if (fhirVersion.startsWith("5.0") || "current".equals(fhirVersion)) { if (fn.endsWith(".xml") && !fn.endsWith("template.xml")) r = new XmlParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) r = new JsonParser().parse(new ByteArrayInputStream(content)); else if (fn.endsWith(".txt")) r = new StructureMapUtilities(getContext(), null, null).parse(TextFile.bytesToString(content), fn); - else if (fn.endsWith(".map")) - r = new StructureMapUtilities(null).parse(new String(content), fn); + else if (fn.endsWith(".map") || fn.endsWith(".fml")) + r = new StructureMapUtilities(context).parse(new String(content), fn); else throw new FHIRException("Unsupported format for " + fn); } else diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 6a89b8310..b04abb4c2 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -219,6 +219,8 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP @Getter @Setter private Coding jurisdiction; + private ContextUtilities cu = null; + /** * Creating a validation engine is an expensive operation - takes seconds. * Once you have a validation engine created, you can quickly clone it to @@ -842,7 +844,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP new org.hl7.fhir.dstu3.formats.XmlParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.dstu3.formats.JsonParser().setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(s, res); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) TextFile.stringToStream(org.hl7.fhir.dstu3.utils.StructureMapUtilities.render((org.hl7.fhir.dstu3.model.StructureMap) res), s, false); else throw new FHIRException("Unsupported format for " + fn); @@ -852,7 +854,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP new org.hl7.fhir.r4.formats.XmlParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(s, res); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) TextFile.stringToStream(org.hl7.fhir.r4.utils.StructureMapUtilities.render((org.hl7.fhir.r4.model.StructureMap) res), s, false); else throw new FHIRException("Unsupported format for " + fn); @@ -877,7 +879,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP new XmlParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); else if (fn.endsWith(".json") && !fn.endsWith("template.json")) new JsonParser().setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(s, r); - else if (fn.endsWith(".txt") || fn.endsWith(".map")) + else if (fn.endsWith(".txt") || fn.endsWith(".map") || fn.endsWith(".fml")) TextFile.stringToStream(StructureMapUtilities.render((org.hl7.fhir.r5.model.StructureMap) r), s, false); else throw new FHIRException("Unsupported format for " + fn); @@ -1061,6 +1063,16 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP resolvedUrls.put(type+"|"+url, false); return false; // todo... how to access settings from here? } + if (url.contains("*") && !url.contains("?")) { + if (cu == null) { + cu = new ContextUtilities(context); + } + List maps = cu.listMaps(url); + if (!maps.isEmpty()) { + return true; + } + + } if (fetcher != null) { try { boolean ok = fetcher.resolveURL(validator, appContext, path, url, type); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java index ce364c1f1..3ca4164a0 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/StandAloneValidatorFetcher.java @@ -105,6 +105,8 @@ public class StandAloneValidatorFetcher implements IValidatorResourceFetcher, IV } if (base.equals("http://terminology.hl7.org")) { pid = "hl7.terminology"; + } else if (base.equals("http://hl7.org/fhir")) { + return false; } else if (url.startsWith("http://hl7.org/fhir")) { pid = pcm.getPackageId(base); } else { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 9fa755843..59b300b75 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -14,6 +14,7 @@ import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck; public class Params { public static final String VERSION = "-version"; + public static final String ALT_VERSION = "-alt-version"; public static final String OUTPUT = "-output"; public static final String OUTPUT_SUFFIX = "-outputSuffix"; @@ -307,9 +308,29 @@ public class Params { if (version == null) { cliContext.addIg(s); } else { - cliContext.setSv(version); + String v = getParam(args, VERSION); + if (v != null && !v.equals(version)) { + throw new Error("Parameters are inconsistent: specified version is "+v+" but -ig parameter "+s+" implies a different version"); + } else if (cliContext.getSv() != null && !version.equals(cliContext.getSv())) { + throw new Error("Parameters are inconsistent: multiple -ig parameters implying differetion versions ("+cliContext.getSv()+","+version+")"); + } else { + cliContext.setSv(version); + } } } + } else if (args[i].equals(ALT_VERSION)) { + if (i + 1 == args.length) + throw new Error("Specified " + args[i] + " without indicating version"); + else { + String s = args[++i]; + String v = VersionUtilities.getMajMin(s); + if (v == null) { + throw new Error("Unsupported FHIR Version "+s); + } + String pid = VersionUtilities.packageForVersion(v); + pid = pid + "#"+VersionUtilities.getCurrentPackageVersion(v); + cliContext.addIg(pid); + } } else if (args[i].equals(MAP)) { if (cliContext.getMap() == null) { if (i + 1 == args.length) @@ -336,6 +357,7 @@ public class Params { cliContext.addSource(args[i]); } } + return cliContext; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java index 7ff2167a0..9d7620f9b 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureMapValidator.java @@ -18,6 +18,7 @@ import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.PathEngineException; import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; +import org.hl7.fhir.r5.context.ContextUtilities; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.elementmodel.Element; @@ -34,6 +35,9 @@ import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.model.StructureMap; +import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent; +import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupInputComponent; +import org.hl7.fhir.r5.model.StructureMap.StructureMapInputMode; import org.hl7.fhir.r5.model.TypeDetails; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.utils.FHIRPathEngine; @@ -226,6 +230,8 @@ public class StructureMapValidator extends BaseValidator { private FHIRPathEngine fpe; private ProfileUtilities profileUtilities; + private ContextUtilities cu; + private List imports = new ArrayList<>(); public StructureMapValidator(IWorkerContext context, TimeTracker timeTracker, FHIRPathEngine fpe, XVerExtensionManager xverManager, ProfileUtilities profileUtilities, Coding jurisdiction) { super(context, xverManager); @@ -234,13 +240,21 @@ public class StructureMapValidator extends BaseValidator { this.timeTracker = timeTracker; this.jurisdiction = jurisdiction; this.profileUtilities = profileUtilities; + this.cu = new ContextUtilities(context); } public boolean validateStructureMap(List errors, Element src, NodeStack stack) { boolean ok = true; - List groups = src.getChildrenByName("group"); + List imports = src.getChildrenByName("import"); int cc = 0; + for (Element import_ : imports) { + ok = validateImport(errors, src, import_, stack.push(import_, cc, null, null)) && ok; + cc++; + } + + List groups = src.getChildrenByName("group"); + cc = 0; for (Element group : groups) { ok = validateGroup(errors, src, group, stack.push(group, cc, null, null)) && ok; cc++; @@ -248,14 +262,34 @@ public class StructureMapValidator extends BaseValidator { return ok; } + private boolean validateImport(List errors, Element src, Element import_, NodeStack stack) { + String url = import_.primitiveValue(); + boolean ok = false; + StructureMap map = context.fetchResource(StructureMap.class, url); + if (map != null) { + imports.add(map); + ok = true; + } else if (url.contains("*")) { + List maps = cu.listMaps(url); + ok = !maps.isEmpty(); + imports.addAll(maps); + } + warning(errors, "2023-03-01", IssueType.INVALID, import_.line(), import_.col(), stack.getLiteralPath(), ok, I18nConstants.SM_IMPORT_NOT_FOUND, url); + return true; + } + private boolean validateGroup(List errors, Element src, Element group, NodeStack stack) { String name = group.getChildValue("name"); - boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); + boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, group.line(), group.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); - Element extend = src.getNamedChild("extends"); + Element extend = group.getNamedChild("extends"); if (extend != null) { - rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, extend.line(), extend.col(), stack.push(extend, -1, null, null).getLiteralPath(), false, I18nConstants.SM_EXTENDS_NOT_SUPPORTED, extend.primitiveValue()); - ok = false; + StructureMapGroupComponent grp = resolveGroup(extend.primitiveValue(), src); + if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, extend.line(), extend.col(), stack.push(extend, -1, null, null).getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, extend.primitiveValue())) { + // check inputs + } else { + ok = false; + } } VariableSet variables = new VariableSet(); @@ -280,24 +314,62 @@ public class StructureMapValidator extends BaseValidator { return ok; } + private StructureMapGroupComponent resolveGroup(String grpName, Element src) { + if (grpName == null) { + return null; + } + List groups = src.getChildrenByName("group"); + for (Element group : groups) { + String name = group.getChildValue("name"); + if (grpName.equals(name)) { + return makeGroupComponent(group); + } + } + for (StructureMap map : imports) { + for (StructureMapGroupComponent grp : map.getGroup()) { + if (grpName.equals(grp.getName())) { + return grp; + } + } + } + return null; + } + + private StructureMapGroupComponent makeGroupComponent(Element group) { + StructureMapGroupComponent grp = new StructureMapGroupComponent(); + grp.setName(group.getChildValue("name")); + List inputs = group.getChildrenByName("input"); + for (Element input : inputs) { + StructureMapGroupInputComponent inp = grp.addInput(); + inp.setName(input.getChildValue("name")); + inp.setType(input.getChildValue("type")); + try { + inp.setMode(StructureMapInputMode.fromCode(input.getChildValue("mode"))); + } catch (Exception e) { + // nothing; will be an error elsewhere + } + } + return grp; + } + private boolean validateInput(List errors, Element src, Element group, Element input, NodeStack stack, List structures, VariableSet variables) { boolean ok = false; String name = input.getChildValue("name"); String type = input.getChildValue("type"); String mode = input.getChildValue("mode"); - if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name) && // the name {0} is not valid) - rule(errors, "2023-02-17", IssueType.DUPLICATE, input.line(), input.col(), stack.getLiteralPath(), !variables.hasVariable(name), I18nConstants.SM_GROUP_INPUT_DUPLICATE, name)) { // the name {0} is not valid) + if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name) && // the name {0} is not valid) + rule(errors, "2023-03-01", IssueType.DUPLICATE, input.line(), input.col(), stack.getLiteralPath(), !variables.hasVariable(name), I18nConstants.SM_GROUP_INPUT_DUPLICATE, name)) { // the name {0} is not valid) VariableDefn v = variables.add(name, mode); - if (rule(errors, "2023-02-17", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), Utilities.existsInList(mode, "source", "target"), I18nConstants.SM_GROUP_INPUT_MODE_INVALID, name, mode) && // the group parameter {0} mode {1} isn't valid - warning(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), type != null, I18nConstants.SM_GROUP_INPUT_NO_TYPE, name)) { // the group parameter {0} has no type, so the paths cannot be validated + if (rule(errors, "2023-03-01", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), Utilities.existsInList(mode, "source", "target"), I18nConstants.SM_GROUP_INPUT_MODE_INVALID, name, mode) && // the group parameter {0} mode {1} isn't valid + warning(errors, "2023-03-01", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), type != null, I18nConstants.SM_GROUP_INPUT_NO_TYPE, name)) { // the group parameter {0} has no type, so the paths cannot be validated Element structure = findStructure(structures, type); - if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), structure != null, I18nConstants.SM_GROUP_INPUT_TYPE_NOT_DECLARED, type)) { // the type {0} was not declared and is unknown + if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), structure != null, I18nConstants.SM_GROUP_INPUT_TYPE_NOT_DECLARED, type)) { // the type {0} was not declared and is unknown String url = structure.getChildValue("url"); String smode = structure.getChildValue("mode"); StructureDefinition sd = context.fetchResource(StructureDefinition.class, url); - if (rule(errors, "2023-02-17", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), mode.equals(smode), I18nConstants.SM_GROUP_INPUT_MODE_MISMATCH, type, mode, smode) && // the type {0} has mode {1} which doesn't match the structure definition {2} - rule(errors, "2023-02-17", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), sd != null, I18nConstants.SM_GROUP_INPUT_TYPE_UNKNOWN, type, url)) { // the type {0} which maps to the canonical URL {1} is not known, so the paths cannot be validated + if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, input.line(), input.col(), stack.getLiteralPath(), mode.equals(smode), I18nConstants.SM_GROUP_INPUT_MODE_MISMATCH, type, mode, smode) && // the type {0} has mode {1} which doesn't match the structure definition {2} + rule(errors, "2023-03-01", IssueType.INVALID, input.line(), input.col(), stack.getLiteralPath(), sd != null, I18nConstants.SM_GROUP_INPUT_TYPE_UNKNOWN, type, url)) { // the type {0} which maps to the canonical URL {1} is not known, so the paths cannot be validated v.setType(1, sd, sd.getSnapshot().getElementFirstRep(), null); ok = true; } @@ -323,7 +395,7 @@ public class StructureMapValidator extends BaseValidator { private boolean validateRule(List errors, Element src, Element group, Element rule, NodeStack stack, VariableSet variables) { String name = rule.getChildValue("name"); - boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, rule.line(), rule.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); + boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, rule.line(), rule.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name); RuleInformation ruleInfo = new RuleInformation(); // process the sources @@ -350,14 +422,19 @@ public class StructureMapValidator extends BaseValidator { cc++; } // todo: check dependents - + List dependents = rule.getChildrenByName("dependent"); + cc = 0; + for (Element dependent : dependents) { + ok = validateDependent(errors, src, group, dependent, stack.push(dependent, cc, null, null), lvars) && ok; + cc++; + } return ok; } private boolean validateRuleSource(List errors, Element src, Element group, Element rule, Element source, NodeStack stack, VariableSet variables, RuleInformation ruleInfo) { String context = source.getChildValue("context"); - boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) && - rule(errors, "2023-02-17", IssueType.UNKNOWN, source.line(), source.col(), stack.getLiteralPath(), variables.hasVariable(context, SOURCE), I18nConstants.SM_SOURCE_CONTEXT_UNKNOWN, context); + boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) && + rule(errors, "2023-03-01", IssueType.UNKNOWN, source.line(), source.col(), stack.getLiteralPath(), variables.hasVariable(context, SOURCE), I18nConstants.SM_SOURCE_CONTEXT_UNKNOWN, context); if (ok) { VariableDefn v = variables.getVariable(context, SOURCE); if (v.hasTypeInfo()) { // if it doesn't, that's already an issue elsewhere @@ -369,8 +446,8 @@ public class StructureMapValidator extends BaseValidator { String path = v.getEd().getPath()+"."+element; String variable = source.getChildValue("variable"); VariableDefn vn = null; - if (hint(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), variable != null, I18nConstants.SM_RULE_SOURCE_UNASSIGNED)) { - if (rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) { + if (hint(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), variable != null, I18nConstants.SM_RULE_SOURCE_UNASSIGNED)) { + if (rule(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) { vn = variables.add(variable, v.getMode()); // may overwrite } else { ok = false; @@ -378,20 +455,20 @@ public class StructureMapValidator extends BaseValidator { } List els = getElementDefinitions(v.getSd(), v.getEd(), v.getType(), element); - if (rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_SOURCE_PATH_INVALID, context, element, path)) { - if (warning(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) { + if (rule(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_SOURCE_PATH_INVALID, context, element, path)) { + if (warning(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) { ElementDefinitionSource el = els.get(0); String type = source.getChildValue("type"); if (type != null) { - ok = rule(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), hasType(el.getEd(), type), I18nConstants.SM_SOURCE_TYPE_INVALID, type, path, el.getEd().typeSummary()) && ok; + ok = rule(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), hasType(el.getEd(), type), I18nConstants.SM_SOURCE_TYPE_INVALID, type, path, el.getEd().typeSummary()) && ok; } String min = source.getChildValue("min"); - hint(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), min == null || isMoreOrEqual(min, v.getEd().getMin()), I18nConstants.SM_RULE_SOURCE_MIN_REDUNDANT, min, v.getEd().getMin()); + hint(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), min == null || isMoreOrEqual(min, v.getEd().getMin()), I18nConstants.SM_RULE_SOURCE_MIN_REDUNDANT, min, v.getEd().getMin()); int existingMax = multiplyCardinality(v.getMax(), el.getEd().getMax()); String max = source.getChildValue("max"); int iMax = readMax(max, existingMax); - warning(errors, "2023-02-17", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), iMax <= existingMax, I18nConstants.SM_RULE_SOURCE_MAX_REDUNDANT, max, v.getMax()); + warning(errors, "2023-03-01", IssueType.INVALID, source.line(), source.col(), stack.getLiteralPath(), iMax <= existingMax, I18nConstants.SM_RULE_SOURCE_MAX_REDUNDANT, max, v.getMax()); ruleInfo.seeCardinality(iMax); @@ -440,22 +517,22 @@ public class StructureMapValidator extends BaseValidator { if (context == null) { return true; } - boolean ok = rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) && - rule(errors, "2023-02-17", IssueType.UNKNOWN, target.line(), target.col(), stack.getLiteralPath(), variables.hasVariable(context, TARGET), I18nConstants.SM_TARGET_CONTEXT_UNKNOWN, context); + boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) && + rule(errors, "2023-03-01", IssueType.UNKNOWN, target.line(), target.col(), stack.getLiteralPath(), variables.hasVariable(context, TARGET), I18nConstants.SM_TARGET_CONTEXT_UNKNOWN, context); if (ok) { VariableDefn v = variables.getVariable(context, TARGET); if (v.hasTypeInfo()) { String listMode = target.getChildValue("listMode"); String listRuleId = target.getChildValue("listRuleId"); - warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null || "share".equals(listMode), I18nConstants.SM_LIST_RULE_ID_ONLY_WHEN_SHARE); + warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null || "share".equals(listMode), I18nConstants.SM_LIST_RULE_ID_ONLY_WHEN_SHARE); if (!ruleInfo.isList()) { - warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listMode == null, I18nConstants.SM_NO_LIST_MODE_NEEDED); - warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null, I18nConstants.SM_NO_LIST_RULE_ID_NEEDED); + warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listMode == null, I18nConstants.SM_NO_LIST_MODE_NEEDED); + warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), listRuleId == null, I18nConstants.SM_NO_LIST_RULE_ID_NEEDED); } VariableDefn vn = null; String variable = target.getChildValue("variable"); if (variable != null) { - if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) { + if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) { vn = variables.add(variable, v.getMode()); // may overwrite } else { ok = false; @@ -465,21 +542,21 @@ public class StructureMapValidator extends BaseValidator { String element = target.getChildValue("element"); if (element != null) { List els = getElementDefinitions(v.getSd(), v.getEd(), v.getType(), element); - if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_TARGET_PATH_INVALID, context, element, v.getEd().getPath()+"."+element)) { - if (warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) { + if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), !els.isEmpty(), I18nConstants.SM_TARGET_PATH_INVALID, context, element, v.getEd().getPath()+"."+element)) { + if (warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), els.size() == 1, I18nConstants.SM_TARGET_PATH_MULTIPLE_MATCHES, context, element, v.getEd().getPath()+"."+element, render(els))) { ElementDefinitionSource el = els.get(0); String type = null; // maybe inferred / derived from transform in the future String transform = target.getChildValue("transform"); List params = target.getChildren("parameter"); if (transform == null) { - rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 0, I18nConstants.SM_TARGET_NO_TRANSFORM_NO_CHECKED, transform); + rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 0, I18nConstants.SM_TARGET_NO_TRANSFORM_NO_CHECKED, transform); } else { switch (transform) { case "create": - if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() < 2, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "create", "0", "1", params.size())) { + if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() < 2, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "create", "0", "1", params.size())) { if (params.size() == 1) { type = params.get(0).primitiveValue(); - warning(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(),type != null, I18nConstants.SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE); + warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(),type != null, I18nConstants.SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE); } else { // maybe can guess? maybe not ... type = } @@ -488,23 +565,23 @@ public class StructureMapValidator extends BaseValidator { } break; case "reference": - if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "reference", "0", "1", params.size())) { + if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "reference", "0", "1", params.size())) { type = "string"; } else { ok = false; } break; case "evaluate": - if (rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE, "evaluate", "1", params.size())) { + if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 1, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE, "evaluate", "1", params.size())) { String exp = params.get(0).primitiveValue(); - if (rule(errors, "2023-02-17", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), exp != null, I18nConstants.SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE, "0", params.size())) { + if (rule(errors, "2023-03-01", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), exp != null, I18nConstants.SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE, "0", params.size())) { try { TypeDetails td = fpe.check(null, v.getSd().getType(), v.getEd().getPath(), fpe.parse(exp)); if (td.getTypes().size() == 1) { type = td.getType(); } } catch (Exception e) { - rule(errors, "2023-02-17", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_EXPRESSION_ERROR, e.getMessage()); + rule(errors, "2023-03-01", IssueType.INVALID, params.get(0).line(), params.get(0).col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_EXPRESSION_ERROR, e.getMessage()); } } else { ok = false; @@ -514,7 +591,7 @@ public class StructureMapValidator extends BaseValidator { } break; default: - rule(errors, "2023-02-17", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform); + rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform); ok = false; } } @@ -590,4 +667,17 @@ public class StructureMapValidator extends BaseValidator { return true; // no issue in this case } + + private boolean validateDependent(List errors, Element src, Element group, Element dependent, NodeStack stack, VariableSet lvars) { + boolean ok = true; + String name = dependent.getChildValue("name"); + StructureMapGroupComponent grp = resolveGroup(name, src); + if (rule(errors, "2023-03-01", IssueType.NOTSUPPORTED, dependent.line(), dependent.col(), stack.push(dependent, -1, null, null).getLiteralPath(), grp != null, I18nConstants.SM_RULEGROUP_NOT_FOUND, name)) { + // check inputs + } else { + ok = false; + } + return ok; + } + }