Merge pull request #1139 from hapifhir/gg-202302-map-validation-2

Work on StructureMap validation
This commit is contained in:
Grahame Grieve 2023-02-27 09:14:13 +11:00 committed by GitHub
commit a62c8683f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 476 additions and 259 deletions

View File

@ -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 {
}
}
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"));
}
}
}

View File

@ -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<CodeSystem> getCodeSystems() {
return new ArrayList<>();
}
@Override
protected String versionString() {
return "4.3";
}
}

View File

@ -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<CodeSystem> getCodeSystems() {
List<CodeSystem> list = new ArrayList<>();
@ -160,4 +139,9 @@ public class R2ToR5Loader extends BaseLoaderR5 implements IContextResourceLoader
return list;
}
@Override
protected String versionString() {
return "1.0";
}
}

View File

@ -99,14 +99,8 @@ 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());
}
}
}
@ -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<CodeSystem> getCodeSystems() {
return new ArrayList<>();
}
@Override
protected String versionString() {
return "3.0";
}
}

View File

@ -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<CodeSystem> getCodeSystems() {
return new ArrayList<>();
}
@Override
protected String versionString() {
return "4.3";
}
}

View File

@ -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<CodeSystem> getCodeSystems() {
return new ArrayList<>();
}
@Override
protected String versionString() {
return "4.0";
}
}

View File

@ -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<CodeSystem> getCodeSystems() {
return new ArrayList<>();
}
@Override
protected String versionString() {
return "5.0";
}
}

View File

@ -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) {

View File

@ -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<StructureMap> listMaps(String url) {
List<StructureMap> 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;
}
}

View File

@ -321,6 +321,22 @@ public interface IWorkerContext {
* @return
*/
List<CodeSystem> 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);
}
/**

View File

@ -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;
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -6352,5 +6352,8 @@ public class FHIRPathEngine {
this.liquidMode = liquidMode;
}
public ProfileUtilities getProfileUtilities() {
return profileUtilities;
}
}

View File

@ -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");

View File

@ -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) {

View File

@ -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";
}

View File

@ -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

View File

@ -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

View File

@ -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<StructureMap> maps = cu.listMaps(url);
if (!maps.isEmpty()) {
return true;
}
}
if (fetcher != null) {
try {
boolean ok = fetcher.resolveURL(validator, appContext, path, url, type);

View File

@ -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 {

View File

@ -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;
}

View File

@ -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<StructureMap> 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<ValidationMessage> errors, Element src, NodeStack stack) {
boolean ok = true;
List<Element> groups = src.getChildrenByName("group");
List<Element> 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<Element> 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<ValidationMessage> 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<StructureMap> 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<ValidationMessage> 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<Element> 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<Element> 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<ValidationMessage> errors, Element src, Element group, Element input, NodeStack stack, List<Element> 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<ValidationMessage> 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<Element> 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<ValidationMessage> 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<ElementDefinitionSource> 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<ElementDefinitionSource> 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<Element> 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<ValidationMessage> 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;
}
}