Gg 202109 misc snapshot (#610)

* NPE fixes

* Smart Health Cards support in validator

* Fix bug generating spreadsheets due to sheet name length limitations

* Implement descendent-of filter

* more NPE fixes

* add Element.removeChild

* fix issue generation snapshot and content reference, and work around old erroneous binding description in R4

* improve SHC validation error

* fix for NPE generating ConceptMap spreadsheet

* fix crash in IG publisher rendering illegal content

* Improve slicing error messages

* more improving error message resolving slicing

* add missing code + track prohibited / required elements (improve rendering of IGs)

* fix for broken links in R4B IGs
This commit is contained in:
Grahame Grieve 2021-09-30 00:29:10 +10:00 committed by GitHub
parent 72b7300700
commit 342d75b2df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 769 additions and 154 deletions

View File

@ -111,11 +111,15 @@ public class ComparisonRenderer implements IEvaluationContext {
}
private void dumpBinaries() throws IOException {
for (String k : contextLeft.getBinaries().keySet()) {
TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k));
if (contextLeft != null && contextLeft.getBinaries() != null) {
for (String k : contextLeft.getBinaries().keySet()) {
TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k));
}
}
for (String k : contextRight.getBinaries().keySet()) {
TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k));
if (contextRight != null && contextRight.getBinaries() != null) {
for (String k : contextRight.getBinaries().keySet()) {
TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k));
}
}
}

View File

@ -109,6 +109,7 @@ import org.hl7.fhir.r5.renderers.spreadsheets.SpreadsheetGenerator;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.FHIRLexer;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.PublicationHacker;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.TranslatingUtilities;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
@ -666,6 +667,7 @@ public class ProfileUtilities extends TranslatingUtilities {
throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
updateMaps(base, derived);
setIds(derived, false);
if (debug) {
System.out.println("Differential: ");
for (ElementDefinition ed : derived.getDifferential().getElement())
@ -674,7 +676,6 @@ public class ProfileUtilities extends TranslatingUtilities {
for (ElementDefinition ed : derived.getSnapshot().getElement())
System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
}
setIds(derived, false);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
//Check that all differential elements have a corresponding snapshot element
int ce = 0;
@ -702,10 +703,10 @@ public class ProfileUtilities extends TranslatingUtilities {
if (!debug) {
System.out.println("Differential: ");
for (ElementDefinition ed : derived.getDifferential().getElement())
System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
System.out.println("Snapshot: ");
for (ElementDefinition ed : derived.getSnapshot().getElement())
System.out.println(" "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
System.out.println(" "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" "+constraintSummary(ed));
}
if (exception)
throw new DefinitionException(msg);
@ -1053,7 +1054,7 @@ public class ProfileUtilities extends TranslatingUtilities {
private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, ElementDefinition slicer, String typeSlicingPath, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException {
if (debug) {
System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")");
System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", k "+(redirector == null ? "null" : redirector.toString())+")");
}
ElementDefinition res = null;
List<TypeSlice> typeList = new ArrayList<>();
@ -1088,7 +1089,7 @@ public class ProfileUtilities extends TranslatingUtilities {
processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit);
} else {
if (outcome.getType().size() == 0) {
if (outcome.getType().size() == 0 && !outcome.hasContentReference()) {
throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), profileName));
}
boolean nonExtension = false;
@ -1103,19 +1104,46 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
}
if (nonExtension) {
throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
}
StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0));
if (dt == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath));
}
contextName = dt.getUrl();
int start = diffCursor;
while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
diffCursor++;
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
if (nonExtension) {
throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
}
if (outcome.hasContentReference()) {
ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference());
if (tgt == null)
throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference()));
replaceFromContentReference(outcome, tgt.getElement());
if (tgt.getSource() != srcSD) {
base = tgt.getSource().getSnapshot();
int nbc = base.getElement().indexOf(tgt.getElement())+1;
int nbl = nbc;
while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
nbl++;
processPaths(indent+" ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource());
} else {
int nbc = base.getElement().indexOf(tgt.getElement())+1;
int nbl = nbc;
while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
nbl++;
System.out.println("Test!");
processPaths(indent+" ", result, base, differential, nbc, start, nbl-1, diffCursor-1, url, webUrl, profileName, tgt.getElement().getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD);
}
} else {
StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0));
if (dt == null) {
throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath));
}
contextName = dt.getUrl();
if (redirector.isEmpty()) {
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
} else {
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, currentBase, cpath), srcSD);
}
}
}
}
baseCursor++;
@ -1716,8 +1744,9 @@ public class ProfileUtilities extends TranslatingUtilities {
updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description
removeStatusExtensions(outcome);
} else if (!diffMatches.get(0).hasSliceName())
} else if (!diffMatches.get(0).hasSliceName()) {
diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called
}
result.getElement().add(outcome);
@ -2314,7 +2343,12 @@ public class ProfileUtilities extends TranslatingUtilities {
return pathSimple;
// String ptail = pathSimple.substring(contextPath.length() + 1);
if (redirector.size() > 0) {
String ptail = pathSimple.substring(contextPath.length()+1);
String ptail = null;
if (contextPath.length() >= pathSimple.length()) {
ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
} else {
ptail = pathSimple.substring(contextPath.length()+1);
}
return redirector.get(redirector.size()-1).getPath()+"."+ptail;
// return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
} else {
@ -2329,7 +2363,12 @@ public class ProfileUtilities extends TranslatingUtilities {
s = pathSimple;
else {
if (redirector.size() > 0) {
String ptail = pathSimple.substring(redirectSource.length() + 1);
String ptail = null;
if (redirectSource.length() >= pathSimple.length()) {
ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
} else {
ptail = pathSimple.substring(redirectSource.length()+1);
}
// ptail = ptail.substring(ptail.indexOf(".")+1);
s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
} else {
@ -2493,6 +2532,9 @@ public class ProfileUtilities extends TranslatingUtilities {
if (VersionUtilities.isR2Ver(context.getVersion())) {
return "http://hl7.org/fhir/DSTU2/";
}
if (VersionUtilities.isR4BVer(context.getVersion())) {
return "http://hl7.org/fhir/2021Mar/";
}
return "";
}
@ -3346,7 +3388,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(ved.getBinding().getDescription());
c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, ved.getBinding().getDescriptionElement()).asStringValue());
}
}
c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
@ -4584,7 +4626,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(binding.getDescription(), checkForNoChange(binding.getDescriptionElement()));
c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement())));
}
}
for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
@ -4937,7 +4979,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(binding.getDescription());
c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue());
}
}
for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {

View File

@ -105,6 +105,8 @@ public class Element extends Base {
private boolean hasParentForValidator;
private String path;
private List<ValidationMessage> messages;
private boolean prohibited;
private boolean required;
public Element(String name) {
super();
@ -975,4 +977,26 @@ public class Element extends Base {
public List<ValidationMessage> getMessages() {
return messages;
}
public void removeChild(String name) {
children.removeIf(n -> name.equals(n.getName()));
}
public boolean isProhibited() {
return prohibited;
}
public void setProhibited(boolean prohibited) {
this.prohibited = prohibited;
}
public boolean isRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
}

View File

@ -36,6 +36,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
@ -108,8 +109,9 @@ public class JsonParser extends ParserBase {
@Override
public Element parse(InputStream stream) throws IOException, FHIRException {
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRException {
// if we're parsing at this point, then we're going to use the custom parser
List<NamedElement> res = new ArrayList<>();
map = new IdentityHashMap<JsonElement, LocationData>();
String source = TextFile.streamToString(stream);
if (policy == ValidationPolicy.EVERYTHING) {
@ -121,12 +123,19 @@ public class JsonParser extends ParserBase {
return null;
}
assert (map.containsKey(obj));
return parse(obj);
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
} else {
JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source);
// assert (map.containsKey(obj));
return parse(obj);
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}
public Element parse(JsonObject object, Map<JsonElement, LocationData> map) throws FHIRException {

View File

@ -34,18 +34,21 @@ package org.hl7.fhir.r5.elementmodel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.StructureDefinition;
public class Manager {
//TODO use EnumMap
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR;
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC;
// SHC = smart health cards, including as text versions of QR codes
public String getExtension() {
switch (this) {
@ -82,10 +85,15 @@ public class Manager {
}
public static Element parse(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException {
public static List<NamedElement> parse(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException {
return makeParser(context, inputFormat).parse(source);
}
public static Element parseSingle(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException {
return makeParser(context, inputFormat).parseSingle(source);
}
public static void compose(IWorkerContext context, Element e, OutputStream destination, FhirFormat outputFormat, OutputStyle style, String base) throws FHIRException, IOException {
makeParser(context, outputFormat).compose(e, destination, style, base);
}
@ -96,6 +104,7 @@ public class Manager {
case XML : return new XmlParser(context);
case TURTLE : return new TurtleParser(context);
case VBAR : return new VerticalBarParser(context);
case SHC : return new SHCParser(context);
case TEXT : throw new Error("Programming logic error: do not call makeParser for a text resource");
}
return null;

View File

@ -38,6 +38,7 @@ import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CodeableConcept;
@ -70,7 +71,11 @@ public class ObjectConverter {
org.hl7.fhir.r5.formats.JsonParser jp = new org.hl7.fhir.r5.formats.JsonParser();
jp.compose(bs, ig);
ByteArrayInputStream bi = new ByteArrayInputStream(bs.toByteArray());
return new JsonParser(context).parse(bi);
List<NamedElement> list = new JsonParser(context).parse(bi);
if (list.size() != 1) {
throw new FHIRException("Unable to convert because the source contains multieple resources");
}
return list.get(0).getElement();
}
public Element convert(Property property, DataType type) throws FHIRException {

View File

@ -40,6 +40,7 @@ import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.FormatUtilities;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -54,6 +55,23 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public abstract class ParserBase {
public class NamedElement {
private String name;
private Element element;
public NamedElement(String name, Element element) {
super();
this.name = name;
this.element = element;
}
public String getName() {
return name;
}
public Element getElement() {
return element;
}
}
public interface ILinkResolver {
String resolveType(String type);
String resolveProperty(Property property);
@ -86,7 +104,15 @@ public abstract class ParserBase {
this.errors = errors;
}
public abstract Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
public abstract List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
public Element parseSingle(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
List<NamedElement> res = parse(stream);
if (res.size() != 1) {
throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed");
}
return res.get(0).getElement();
}
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException;
@ -161,5 +187,9 @@ public abstract class ParserBase {
this.showDecorations = showDecorations;
}
public String getImpliedProfile() {
return null;
}
}

View File

@ -0,0 +1,310 @@
package org.hl7.fhir.r5.elementmodel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* this class is actually a smart health cards validator.
* It's going to parse the JWT and assume that it contains
* a smart health card, which has a nested bundle in it, and
* then validate the bundle.
*
* See https://spec.smarthealth.cards/#health-cards-are-encoded-as-compact-serialization-json-web-signatures-jws
*
* This parser dose the JWT work, and then passes the JsonObject through to the underlying JsonParser
*
* Error locations are in the decoded payload
*
* @author grahame
*
*/
public class SHCParser extends ParserBase {
private JsonParser jsonParser;
private Map<JsonElement, LocationData> map;
private List<String> types = new ArrayList<>();
public SHCParser(IWorkerContext context) {
super(context);
jsonParser = new JsonParser(context);
}
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
List<NamedElement> res = new ArrayList<>();
String src = TextFile.streamToString(stream).trim();
List<String> list = new ArrayList<>();
String pfx = null;
if (src.startsWith("{")) {
JsonObject json = JsonTrackingParser.parseJson(src);
if (checkProperty(json, "$", "verifiableCredential", true, "Array")) {
pfx = "verifiableCredential";
JsonArray arr = json.getAsJsonArray("verifiableCredential");
int i = 0;
for (JsonElement e : arr) {
if (!(e instanceof JsonPrimitive)) {
logError(line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR);
} else {
list.add(e.getAsString());
}
i++;
}
} else {
return res;
}
} else {
list.add(src);
}
int c = 0;
for (String ssrc : list) {
String prefix = pfx == null ? "" : pfx+"["+Integer.toString(c)+"].";
c++;
JWT jwt = null;
try {
jwt = decodeJWT(ssrc);
} catch (Exception e) {
logError(1, 1, prefix+"JWT", IssueType.INVALID, "Unable to decode JWT token", IssueSeverity.ERROR);
return res;
}
map = jwt.map;
JsonTrackingParser.write(jwt.payload, "c:\\temp\\payload.json");
checkNamedProperties(jwt.getPayload(), prefix+"payload", "iss", "nbf", "vc");
checkProperty(jwt.getPayload(), prefix+"payload", "iss", true, "String");
logError(1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+
"(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().get("iss").getAsString()+"')", IssueSeverity.INFORMATION);
checkProperty(jwt.getPayload(), prefix+"payload", "nbf", true, "Number");
JsonObject vc = jwt.getPayload().getAsJsonObject("vc");
if (vc == null) {
logError(1, 1, "JWT", IssueType.STRUCTURE, "Unable to find property 'vc' in the payload", IssueSeverity.ERROR);
return res;
}
String path = prefix+"payload.vc";
checkNamedProperties(vc, path, "type", "credentialSubject");
if (!checkProperty(vc, path, "type", true, "Array")) {
return res;
}
JsonArray type = vc.getAsJsonArray("type");
int i = 0;
for (JsonElement e : type) {
if (!(e instanceof JsonPrimitive)) {
logError(line(e), col(e), path+".type["+i+"]", IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR);
} else {
types.add(e.getAsString());
}
i++;
}
if (!types.contains("https://smarthealth.cards#health-card")) {
logError(line(vc), col(vc), path, IssueType.STRUCTURE, "Card does not claim to be of type https://smarthealth.cards#health-card, cannot validate", IssueSeverity.ERROR);
return res;
}
if (!checkProperty(vc, path, "credentialSubject", true, "Object")) {
return res;
}
JsonObject cs = vc.getAsJsonObject("credentialSubject");
path = path+".credentialSubject";
if (!checkProperty(cs, path, "fhirVersion", true, "String")) {
return res;
}
JsonElement fv = cs.get("fhirVersion");
if (!VersionUtilities.versionsCompatible(context.getVersion(), fv.getAsString())) {
logError(line(fv), col(fv), path+".fhirVersion", IssueType.STRUCTURE, "Card claims to be of version "+fv.getAsString()+", cannot be validated against version "+context.getVersion(), IssueSeverity.ERROR);
return res;
}
if (!checkProperty(cs, path, "fhirBundle", true, "Object")) {
return res;
}
// ok. all checks passed, we can now validate the bundle
Element e = jsonParser.parse(cs.getAsJsonObject("fhirBundle"), map);
if (e != null) {
res.add(new NamedElement(path, e));
}
}
return res;
}
@Override
public String getImpliedProfile() {
if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#immunization")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-vaccination-bundle-dm";
}
if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#laboratory")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-covid19-laboratory-bundle-dm";
}
if (types.contains("https://smarthealth.cards#laboratory")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-infectious-disease-laboratory-bundle-dm";
}
return null;
}
private boolean checkProperty(JsonObject obj, String path, String name, boolean required, String type) {
JsonElement e = obj.get(name);
if (e != null) {
String t = JSONUtil.type(e);
if (!type.equals(t)) {
logError(line(e), col(e), path+"."+name, IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : "+type+" but found "+t, IssueSeverity.ERROR);
} else {
return true;
}
} else if (required) {
logError(line(obj), col(obj), path, IssueType.STRUCTURE, "Missing Property in JSON Payload: "+name, IssueSeverity.ERROR);
} else {
return true;
}
return false;
}
private void checkNamedProperties(JsonObject obj, String path, String... names) {
for (Entry<String, JsonElement> e : obj.entrySet()) {
if (!Utilities.existsInList(e.getKey(), names)) {
logError(line(e.getValue()), col(e.getValue()), path+"."+e.getKey(), IssueType.STRUCTURE, "Unknown Property in JSON Payload", IssueSeverity.WARNING);
}
}
}
private int line(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getLine();
}
private int col(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getCol();
}
public void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException {
throw new FHIRFormatError("Writing resources is not supported for the SHC format");
// because then we'd have to try to sign, and we're just not going to be doing that from the element model
}
public static class JWT {
private JsonObject header;
private JsonObject payload;
public Map<JsonElement, LocationData> map = new HashMap<>();
public JsonObject getHeader() {
return header;
}
public void setHeader(JsonObject header) {
this.header = header;
}
public JsonObject getPayload() {
return payload;
}
public void setPayload(JsonObject payload) {
this.payload = payload;
}
}
private static final int BUFFER_SIZE = 1024;
public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2";
// todo: deal with chunking
public static String decodeQRCode(String src) {
StringBuilder b = new StringBuilder();
if (!src.startsWith("shc:/")) {
throw new FHIRException("Unable to process smart health card (didn't start with shc:/)");
}
for (int i = 5; i < src.length(); i = i + 2) {
String s = src.substring(i, i+2);
byte v = Byte.parseByte(s);
char c = (char) (45+v);
b.append(c);
}
return b.toString();
}
public static JWT decodeJWT(String jwt) throws IOException, DataFormatException {
if (jwt.startsWith("shc:/")) {
jwt = decodeQRCode(jwt);
}
String[] parts = splitToken(jwt);
byte[] headerJson;
byte[] payloadJson;
try {
headerJson = Base64.getUrlDecoder().decode(parts[0]);
payloadJson = Base64.getUrlDecoder().decode(parts[1]);
} catch (NullPointerException e) {
throw new FHIRException("The UTF-8 Charset isn't initialized.", e);
} catch (IllegalArgumentException e){
throw new FHIRException("The input is not a valid base 64 encoded string.", e);
}
JWT res = new JWT();
res.header = JsonTrackingParser.parseJson(headerJson);
if ("DEF".equals(JSONUtil.str(res.header, "zip"))) {
payloadJson = inflate(payloadJson);
}
res.payload = JsonTrackingParser.parse(TextFile.bytesToString(payloadJson), res.map, true);
return res;
}
static String[] splitToken(String token) {
String[] parts = token.split("\\.");
if (parts.length == 2 && token.endsWith(".")) {
//Tokens with alg='none' have empty String as Signature.
parts = new String[]{parts[0], parts[1], ""};
}
if (parts.length != 3) {
throw new FHIRException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
}
return parts;
}
public static final byte[] inflate(byte[] data) throws IOException, DataFormatException {
final Inflater inflater = new Inflater(true);
inflater.setInput(data);
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length))
{
byte[] buffer = new byte[BUFFER_SIZE];
while (!inflater.finished())
{
final int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
return outputStream.toByteArray();
}
}
}

View File

@ -34,11 +34,13 @@ package org.hl7.fhir.r5.elementmodel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map.Entry;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
@ -61,7 +63,7 @@ public class Tester {
// new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json")), FhirFormat.JSON, OutputStyle.PRETTY);
// String src = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json")));
// String tgt = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".json")));
Element e = Manager.parse(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML);
Element e = Manager.parseSingle(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML);
Manager.compose(context, e, new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl")), FhirFormat.TURTLE, OutputStyle.PRETTY, null);
Manager.compose(context, e, new FileOutputStream("C:\\temp\\resource.xml"), FhirFormat.XML, OutputStyle.PRETTY, null);
String src = TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl"));

View File

@ -34,6 +34,7 @@ package org.hl7.fhir.r5.elementmodel;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -42,6 +43,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -74,7 +76,8 @@ public class TurtleParser extends ParserBase {
super(context);
}
@Override
public Element parse(InputStream input) throws IOException, FHIRException {
public List<NamedElement> parse(InputStream input) throws IOException, FHIRException {
List<NamedElement> res = new ArrayList<>();
Turtle src = new Turtle();
if (policy == ValidationPolicy.EVERYTHING) {
try {
@ -83,11 +86,18 @@ public class TurtleParser extends ParserBase {
logError(-1, -1, "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_TURTLE_, e.getMessage()), IssueSeverity.FATAL);
return null;
}
return parse(src);
Element e = parse(src);
if (e != null) {
res.add(new NamedElement(null, e));
}
} else {
src.parse(TextFile.streamToString(input));
return parse(src);
src.parse(TextFile.streamToString(input));
Element e = parse(src);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}
private Element parse(Turtle src) throws FHIRException {

View File

@ -36,11 +36,14 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -450,7 +453,7 @@ public class VerticalBarParser extends ParserBase {
private Delimiters delimiters = new Delimiters();
@Override
public Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message");
Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd));
VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset);
@ -458,8 +461,9 @@ public class VerticalBarParser extends ParserBase {
preDecode(reader);
while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size()))
readSegment(message, reader);
return message;
List<NamedElement> res = new ArrayList<>();
res.add(new NamedElement(null, message));
return res;
}
private void preDecode(VerticalBarParserReader reader) throws FHIRException {

View File

@ -53,6 +53,7 @@ import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.formats.FormatUtilities;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.DateTimeType;
@ -106,7 +107,8 @@ public class XmlParser extends ParserBase {
this.allowXsiLocation = allowXsiLocation;
}
public Element parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
public List<NamedElement> parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
List<NamedElement> res = new ArrayList<>();
Document doc = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@ -157,10 +159,13 @@ public class XmlParser extends ParserBase {
logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
doc = null;
}
if (doc == null)
return null;
else
return parse(doc);
if (doc != null) {
Element e = parse(doc);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}

View File

@ -32,9 +32,12 @@ public class ConceptMapSpreadsheetGenerator extends CanonicalSpreadsheetGenerato
}
private void addConceptMapMetadata(Sheet sheet, ConceptMap cm) {
addMetadataRow(sheet, "Source", cm.getSource().primitiveValue());
addMetadataRow(sheet, "Target", cm.getTarget().primitiveValue());
if (cm.hasSource()) {
addMetadataRow(sheet, "Source", cm.getSource().primitiveValue());
}
if (cm.hasTarget()) {
addMetadataRow(sheet, "Target", cm.getTarget().primitiveValue());
}
}
private void renderGroup(ConceptMapGroupComponent grp, int i) {

View File

@ -18,9 +18,12 @@ import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.renderers.DataRenderer;
import com.microsoft.schemas.office.visio.x2012.main.ShapeSheetType;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
@ -54,6 +57,8 @@ import org.hl7.fhir.r5.renderers.DataRenderer;
public class SpreadsheetGenerator {
private static final int MAX_SENSITIVE_SHEET_NAME_LEN = 31;
protected IWorkerContext context;
protected XSSFWorkbook wb = new XSSFWorkbook();
@ -76,6 +81,9 @@ public class SpreadsheetGenerator {
}
protected Sheet makeSheet(String name) {
if (name.length() > MAX_SENSITIVE_SHEET_NAME_LEN - 2) {
name = name.substring(0, MAX_SENSITIVE_SHEET_NAME_LEN - 2);
}
String s = name;
if (sheetNames.contains(s)) {
int i = 1;

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.r5.renderers.utils;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@ -16,6 +17,7 @@ import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.renderers.ResourceRenderer;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
@ -65,7 +67,11 @@ public class ElementWrappers {
if (context.getParser() == null) {
throw new Error("No type parser provided to renderer context");
} else {
return context.getParser().parseType(xml.toString(), type);
try {
return context.getParser().parseType(xml.toString(StandardCharsets.UTF_8), type);
} catch (Exception e) {
return new StringType("Illegal syntax: "+e.getMessage());
}
}
}

View File

@ -660,16 +660,17 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe
private boolean codeInConceptFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) throws FHIRException {
switch (f.getOp()) {
case ISA: return codeInConceptIsAFilter(cs, f, code);
case ISNOTA: return !codeInConceptIsAFilter(cs, f, code);
case ISA: return codeInConceptIsAFilter(cs, f, code, false);
case ISNOTA: return !codeInConceptIsAFilter(cs, f, code, false);
case DESCENDENTOF: return codeInConceptIsAFilter(cs, f, code, true);
default:
System.out.println("todo: handle concept filters with op = "+f.getOp());
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__CONCEPT_FILTER_WITH_OP__, cs.getUrl(), f.getOp()));
}
}
private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code) {
if (code.equals(f.getProperty())) {
private boolean codeInConceptIsAFilter(CodeSystem cs, ConceptSetFilterComponent f, String code, boolean rootOnly) {
if (!rootOnly && code.equals(f.getProperty())) {
return true;
}
ConceptDefinitionComponent cc = findCodeInConcept(cs.getConcept(), f.getValue());

View File

@ -5435,7 +5435,7 @@ public class FHIRPathEngine {
* @throws PathEngineException
* @throws DefinitionException
*/
public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element) throws DefinitionException {
public ElementDefinition evaluateDefinition(ExpressionNode expr, StructureDefinition profile, ElementDefinition element, StructureDefinition source) throws DefinitionException {
StructureDefinition sd = profile;
ElementDefinition focus = null;
boolean okToNotResolve = false;
@ -5494,6 +5494,7 @@ public class FHIRPathEngine {
List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(sd, element);
for (ElementDefinition t : childDefinitions) {
if (t.getPath().endsWith(".extension") && t.hasSliceName()) {
System.out.println("t: "+t.getId());
StructureDefinition exsd = (t.getType() == null || t.getType().isEmpty() || t.getType().get(0).getProfile().isEmpty()) ?
null : worker.fetchResource(StructureDefinition.class, t.getType().get(0).getProfile().get(0).getValue());
while (exsd != null && !exsd.getBaseDefinition().equals("http://hl7.org/fhir/StructureDefinition/Extension")) {
@ -5508,6 +5509,9 @@ public class FHIRPathEngine {
}
}
}
if (focus == null) {
throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION, expr.toString(), targetUrl, element.getId(), sd.getUrl());
}
} else if ("ofType".equals(expr.getName())) {
if (!element.hasType()) {
throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_TYPE_NONE, element.getId());
@ -5537,12 +5541,12 @@ public class FHIRPathEngine {
if (okToNotResolve) {
return null;
} else {
throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString());
throw makeException(expr, I18nConstants.FHIRPATH_DISCRIMINATOR_CANT_FIND, expr.toString(), source.getUrl(), element.getId(), profile.getUrl());
}
} else if (expr.getInner() == null) {
return focus;
} else {
return evaluateDefinition(expr.getInner(), sd, focus);
return evaluateDefinition(expr.getInner(), sd, focus, profile);
}
}

View File

@ -281,9 +281,9 @@ public interface IResourceValidator {
* in addition, you can pass one or more profiles ti validate beyond the base standard - as structure definitions or canonical URLs
* @throws IOException
*/
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.r5.elementmodel.Element element) throws FHIRException;
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.r5.elementmodel.Element element, String profile) throws FHIRException;
void validate(Object Context, List<ValidationMessage> errors, org.hl7.fhir.r5.elementmodel.Element element, List<StructureDefinition> profiles) throws FHIRException;
void validate(Object Context, List<ValidationMessage> errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element) throws FHIRException;
void validate(Object Context, List<ValidationMessage> errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element, String profile) throws FHIRException;
void validate(Object Context, List<ValidationMessage> errors, String initialPath, org.hl7.fhir.r5.elementmodel.Element element, List<StructureDefinition> profiles) throws FHIRException;
org.hl7.fhir.r5.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format) throws FHIRException;
org.hl7.fhir.r5.elementmodel.Element validate(Object Context, List<ValidationMessage> errors, InputStream stream, FhirFormat format, String profile) throws FHIRException;

View File

@ -0,0 +1,27 @@
package org.hl7.fhir.r5.utils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.ServiceRequest;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.utilities.Utilities;
public class PublicationHacker {
// this routine fixes up broken binding descriptions from past FHIR publications. All of them will be or are fixed in a later version,
// but fixing old versions is procedurally very difficult. Hence, these work around fixes here
public static StringType fixBindingDescriptions(IWorkerContext context, StringType s) {
StringType res = s.copy();
// ServiceRequest.code
if (res.getValue().contains("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]")) {
res.setValue(res.getValue().replace("LOINC is (preferred)[http://build.fhir.org/terminologies.html#preferred]", "LOINC is [preferred]("+Utilities.pathURL(context.getSpecUrl(), "terminologies.html#preferred)")));
}
if (res.getValue().contains("[here](valueset-diagnostic-requests.html)")) {
res.setValue(res.getValue().replace("[here](valueset-diagnostic-requests.html)", "here"));
}
return res;
}
}

View File

@ -88,7 +88,7 @@ public class FFHIRPathHostServices implements FHIRPathEngine.IEvaluationContext
return noErrorValidationMessages(valerrors);
}
if (item instanceof Element) {
val.validate(appContext, valerrors, (Element) item, url);
val.validate(appContext, valerrors, null, (Element) item, url);
return noErrorValidationMessages(valerrors);
}
throw new NotImplementedException("Not done yet (FFHIRPathHostServices.conformsToProfile), when item is not element or not resource");

View File

@ -207,14 +207,14 @@ public class CDARoundTripTests {
* @throws IOException
*/
public void testClinicalDocumentXmlParser() throws IOException {
Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"),
Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"),
FhirFormat.XML);
assertsExample(cda);
ByteArrayOutputStream baosXml = new ByteArrayOutputStream();
Manager.compose(context, cda, baosXml, FhirFormat.XML, OutputStyle.PRETTY, null);
Element cdaXmlRoundtrip = Manager.parse(context, new ByteArrayInputStream(baosXml.toString().getBytes()), FhirFormat.XML);
Element cdaXmlRoundtrip = Manager.parseSingle(context, new ByteArrayInputStream(baosXml.toString().getBytes()), FhirFormat.XML);
assertsExample(cdaXmlRoundtrip);
}
@ -226,14 +226,14 @@ public class CDARoundTripTests {
* @throws IOException
*/
public void testClinicalDocumentJsonParser() throws IOException {
Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"),
Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"),
FhirFormat.XML);
assertsExample(cda);
ByteArrayOutputStream baosJson = new ByteArrayOutputStream();
Manager.compose(context, cda, baosJson, FhirFormat.JSON, OutputStyle.PRETTY, null);
Element cdaJsonRoundtrip = Manager.parse(context, new ByteArrayInputStream(baosJson.toString().getBytes()),
Element cdaJsonRoundtrip = Manager.parseSingle(context, new ByteArrayInputStream(baosJson.toString().getBytes()),
FhirFormat.JSON);
assertsExample(cdaJsonRoundtrip);
@ -245,7 +245,7 @@ public class CDARoundTripTests {
* verify that umlaut like äö etc are not encoded in UTF-8 in attributes
*/
public void testSerializeUmlaut() throws IOException {
Element xml = Manager.parse(context,
Element xml = Manager.parseSingle(context,
TestingUtilities.loadTestResourceStream("validator", "cda", "example.xml"), FhirFormat.XML);
List<Element> title = xml.getChildrenByName("title");

View File

@ -148,7 +148,7 @@ public class NarrativeGenerationTests {
Assertions.assertTrue(output.equals(target), "Output does not match expected");
if (test.isMeta()) {
org.hl7.fhir.r5.elementmodel.Element e = Manager.parse(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML);
org.hl7.fhir.r5.elementmodel.Element e = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML);
x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e));
target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html"));

View File

@ -66,7 +66,7 @@ public class ResourceRoundTripTests {
* verify that umlaut like äö etc are not encoded in UTF-8 in attributes
*/
public void testSerializeUmlaut() throws IOException {
Element xml = Manager.parse(TestingUtilities.context(), TestingUtilities.loadTestResourceStream("r5", "unicode.xml"),
Element xml = Manager.parseSingle(TestingUtilities.context(), TestingUtilities.loadTestResourceStream("r5", "unicode.xml"),
FhirFormat.XML);
List<Element> concept = xml.getChildrenByName("concept");
assertTrue(concept!=null && concept.size()==1);

View File

@ -30,7 +30,7 @@ public class ValidationTestConvertor {
if (!t.exists()) {
try {
System.out.print("Process " + f.getAbsolutePath());
Element e = Manager.parse(context, new FileInputStream(f), FhirFormat.XML);
Element e = Manager.parseSingle(context, new FileInputStream(f), FhirFormat.XML);
Manager.compose(context, e, new FileOutputStream(t), FhirFormat.TURTLE, OutputStyle.PRETTY, null);
System.out.println(" .... success");
} catch (Exception e) {
@ -44,7 +44,7 @@ public class ValidationTestConvertor {
if (!t.exists()) {
try {
System.out.print("Process " + f.getAbsolutePath());
Element e = Manager.parse(context, new FileInputStream(f), FhirFormat.JSON);
Element e = Manager.parseSingle(context, new FileInputStream(f), FhirFormat.JSON);
Manager.compose(context, e, new FileOutputStream(t), FhirFormat.TURTLE, OutputStyle.PRETTY, null);
System.out.println(" .... success");
} catch (Exception e) {

View File

@ -51,7 +51,7 @@ public class XmlParserTests {
* @throws IOException
*/
public void testXsiDeserialiserXmlParser() throws IOException {
Element cda = Manager.parse(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example-xsi.xml"),
Element cda = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("validator", "cda", "example-xsi.xml"),
FhirFormat.XML);
ByteArrayOutputStream baosXml = new ByteArrayOutputStream();

View File

@ -86,7 +86,7 @@ public class ResourceTest {
}
public Element testEM() throws Exception {
Element resource = Manager.parse(TestingUtilities.context(), new FileInputStream(source), isJson() ? FhirFormat.JSON : FhirFormat.XML);
Element resource = Manager.parseSingle(TestingUtilities.context(), new FileInputStream(source), isJson() ? FhirFormat.JSON : FhirFormat.XML);
Manager.compose(TestingUtilities.context(), resource, new FileOutputStream(source.getAbsoluteFile()+".out.json"), FhirFormat.JSON, OutputStyle.PRETTY, null);
Manager.compose(TestingUtilities.context(), resource, new FileOutputStream(source.getAbsoluteFile()+".out.json"), FhirFormat.XML, OutputStyle.PRETTY, null);
return resource;

View File

@ -649,11 +649,13 @@ public class Utilities {
StringBuilder s = new StringBuilder();
boolean d = false;
for (String arg : args) {
if (!d)
d = !noString(arg);
else if (!s.toString().endsWith("/") && !arg.startsWith("/"))
s.append("/");
s.append(arg);
if (args != null) {
if (!d)
d = !noString(arg);
else if (s.toString() != null && !s.toString().endsWith("/") && !arg.startsWith("/"))
s.append("/");
s.append(arg);
}
}
return s.toString();
}

View File

@ -137,6 +137,7 @@ public class I18nConstants {
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST";
public static final String FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = "FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP";
public static final String FHIRPATH_DISCRIMINATOR_CANT_FIND = "FHIRPATH_DISCRIMINATOR_CANT_FIND";
public static final String FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION = "FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION";
public static final String FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES = "FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES";
public static final String FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES = "FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES";
public static final String FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED = "FHIRPATH_DISCRIMINATOR_NAME_ALREADY_SLICED";

View File

@ -45,6 +45,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
public class JSONUtil {
@ -139,4 +140,27 @@ public class JSONUtil {
return (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(c.getInputStream()));
}
public static String type(JsonElement e) {
if (e == null) {
return "(null)";
}
if (e.isJsonObject()) {
return "Object";
}
if (e.isJsonArray()) {
return "Array";
}
if (e.isJsonNull()) {
return "Null";
}
JsonPrimitive p = (JsonPrimitive) e;
if (p.isBoolean()) {
return "Boolean";
}
if (p.isNumber()) {
return "Number";
}
return "String";
}
}

View File

@ -138,6 +138,7 @@ public class PackageHacker {
case "file://C:\\GitHub\\hl7.fhir.uv.mhealth-framework#0.1.0\\output": return "http://hl7.org/fhir/uv/mhealth-framework/2020May";
case "file://C:\\GitHub\\hl7.fhir.uv.security-label-ds4p#0.1.0\\output": return "http://hl7.org/fhir/uv/security-label-ds4p/2020May";
case "file://C:\\GitHub\\hl7.fhir.uv.shorthand#0.12.0\\output": return "http://hl7.org/fhir/uv/shorthand/2020May";
case "http://build.fhir.org/branches/R4B//": return "http://hl7.org/fhir/2021Mar";
}
// https://github.com/HL7/fhir-ig-publisher/issues/295

View File

@ -224,7 +224,7 @@ Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - valida
Validation_VAL_Profile_NoType = The type of element {0} is not known, which is illegal. Valid types at this point are {1}
Validation_VAL_Profile_NotAllowed = This element is not allowed by the profile {0}
Validation_VAL_Profile_NotSlice = This element does not match any known slice {0} and slicing is CLOSED: {1}
Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element ''{1}'' is out of order
Validation_VAL_Profile_OutOfOrder = As specified by profile {0}, Element ''{1}'' is out of order (found after {2})
Validation_VAL_Profile_SliceOrder = As specified by profile {0}, Element ''{1}'' is out of order in ordered slice
Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked because it is unknown
VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY = Profile reference ''{0}'' has not been checked because it is unknown, and the validator is set to not fetch unknown profiles
@ -569,7 +569,8 @@ FHIRPATH_DISCRIMINATOR_NO_CODE = illegal use of ofType() in discriminator - Type
FHIRPATH_DISCRIMINATOR_BAD_NAME = illegal function name {0}() in discriminator
FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_GROUP = illegal expression syntax in discriminator (group)
FHIRPATH_DISCRIMINATOR_BAD_SYNTAX_CONST = illegal expression syntax in discriminator (const)
FHIRPATH_DISCRIMINATOR_CANT_FIND = Unable to resolve discriminator in definitions: {0}
FHIRPATH_DISCRIMINATOR_CANT_FIND = Unable to resolve discriminator in definitions: {0} in profile {1} on element {2}, looking in profile {3}
FHIRPATH_DISCRIMINATOR_CANT_FIND_EXTENSION = Unable to resolve discriminator {0} on {2} found in the definitions because the extension {1} wasn''t found in the profile {3}
FHIRPATH_DISCRIMINATOR_NOTYPE = Error in discriminator at {0}: no children, no type
FHIRPATH_DISCRIMINATOR_MULTIPLE_TYPES = Error in discriminator at {0}: no children, multiple types
FHIRPATH_DISCRIMINATOR_MULTIPLE_PROFILES = Error in discriminator at {0}: no children, multiple type profiles

View File

@ -189,7 +189,7 @@ public class IgLoader {
return readZip(new FileInputStream(src));
if (src.endsWith("igpack.zip"))
return readZip(new FileInputStream(src));
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), src);
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src));
@ -321,7 +321,7 @@ public class IgLoader {
return readZip(new FileInputStream(src));
if (src.endsWith("igpack.zip"))
return readZip(new FileInputStream(src));
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), src);
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(f), src, true);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), TextFile.fileToBytesNCS(src));
@ -470,7 +470,7 @@ public class IgLoader {
else
cnt = TextFile.streamToBytes(stream);
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src);
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), cnt, src, true);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "." + fmt.getExtension()), cnt);
@ -569,7 +569,7 @@ public class IgLoader {
if (ff.isDirectory() && recursive) {
res.putAll(scanDirectory(ff, true));
} else if (!ff.isDirectory() && !isIgnoreFile(ff)) {
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), ff.getAbsolutePath());
Manager.FhirFormat fmt = ResourceChecker.checkIsResource(getContext(), isDebug(), TextFile.fileToBytes(ff), ff.getAbsolutePath(), true);
if (fmt != null) {
res.put(Utilities.changeFileExt(ff.getName(), "." + fmt.getExtension()), TextFile.fileToBytes(ff.getAbsolutePath()));
}

View File

@ -2,16 +2,75 @@ package org.hl7.fhir.validation;
import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.SHCParser;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.SHCParser.JWT;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import com.google.gson.JsonObject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ResourceChecker {
protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, byte[] cnt, String filename) {
// protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, String path) throws IOException {
//
// if (Utilities.existsInList(ext, "json"))
// return Manager.FhirFormat.JSON;
// if (Utilities.existsInList(ext, "map"))
// return Manager.FhirFormat.TEXT;
// if (Utilities.existsInList(ext, "txt"))
// return Manager.FhirFormat.TEXT;
// if (Utilities.existsInList(ext, "jwt", "jws"))
// return Manager.FhirFormat.SHC;
//
// return checkIsResource(context, debug, TextFile.fileToBytes(path), path);
// }
public static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, byte[] cnt, String filename, boolean guessFromExtension) {
System.out.println(" ..Detect format for " + filename);
if (guessFromExtension) {
String ext = Utilities.getFileExtension(filename);
if (Utilities.existsInList(ext, "xml")) {
return FhirFormat.XML;
}
if (Utilities.existsInList(ext, "ttl")) {
return FhirFormat.TURTLE;
}
if (Utilities.existsInList(ext, "map")) {
return Manager.FhirFormat.TEXT;
}
if (Utilities.existsInList(ext, "jwt", "jws")) {
return Manager.FhirFormat.SHC;
}
if (Utilities.existsInList(ext, "json")) {
// no, we have to look inside, and decide.
try {
JsonObject json = JsonTrackingParser.parseJson(cnt);
if (json.has("verifiableCredential")) {
return FhirFormat.SHC;
}
} catch (Exception e) {
}
return FhirFormat.JSON;
}
if (Utilities.existsInList(ext, "txt")) {
try {
String src = TextFile.bytesToString(cnt);
if (src.startsWith("shc:/")) {
return FhirFormat.SHC;
}
} catch (Exception e) {
}
return Manager.FhirFormat.TEXT;
}
}
try {
Manager.parse(context, new ByteArrayInputStream(cnt), Manager.FhirFormat.JSON);
return Manager.FhirFormat.JSON;
@ -36,6 +95,17 @@ public class ResourceChecker {
System.out.println("Not Turtle: " + e.getMessage());
}
}
try {
String s = new String(cnt, StandardCharsets.UTF_8);
if (s.startsWith("shc:/"))
s = SHCParser.decodeQRCode(s);
JWT jwt = SHCParser.decodeJWT(s);
return Manager.FhirFormat.SHC;
} catch (Exception e) {
if (debug) {
System.out.println("Not a smart health card: " + e.getMessage());
}
}
try {
new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(cnt), null);
return Manager.FhirFormat.TEXT;
@ -49,19 +119,5 @@ public class ResourceChecker {
return null;
}
protected static Manager.FhirFormat checkIsResource(SimpleWorkerContext context, boolean debug, String path) throws IOException {
String ext = Utilities.getFileExtension(path);
if (Utilities.existsInList(ext, "xml"))
return Manager.FhirFormat.XML;
if (Utilities.existsInList(ext, "json"))
return Manager.FhirFormat.JSON;
if (Utilities.existsInList(ext, "ttl"))
return Manager.FhirFormat.TURTLE;
if (Utilities.existsInList(ext, "map"))
return Manager.FhirFormat.TEXT;
if (Utilities.existsInList(ext, "txt"))
return Manager.FhirFormat.TEXT;
return checkIsResource(context, debug, TextFile.fileToBytes(path), path);
}
}

View File

@ -21,6 +21,7 @@ import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.elementmodel.SHCParser;
import org.hl7.fhir.r5.formats.FormatUtilities;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
@ -293,7 +294,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
// testing entry point
public OperationOutcome validate(FhirFormat format, InputStream stream, List<String> profiles) throws FHIRException, IOException, EOperationOutcome {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
InstanceValidator validator = getValidator();
InstanceValidator validator = getValidator(format);
validator.validate(null, messages, stream, format, asSdList(profiles));
return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
}
@ -350,7 +351,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
}
public OperationOutcome validate(byte[] source, FhirFormat cntType, List<String> profiles, List<ValidationMessage> messages) throws FHIRException, IOException, EOperationOutcome {
InstanceValidator validator = getValidator();
InstanceValidator validator = getValidator(cntType);
validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
return ValidatorUtils.messagesToOutcome(messages, context, fhirPathEngine);
@ -361,7 +362,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
if (doNative) {
SchemaValidator.validateSchema(location, cntType, messages);
}
InstanceValidator validator = getValidator();
InstanceValidator validator = getValidator(cntType);
validator.validate(null, messages, new ByteArrayInputStream(source), cntType, asSdList(profiles));
if (showTimes) {
System.out.println(location + ": " + validator.reportTimes());
@ -377,7 +378,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
if (doNative) {
SchemaValidator.validateSchema(location, cntType, messages);
}
InstanceValidator validator = getValidator();
InstanceValidator validator = getValidator(cntType);
validator.setResourceIdRule(resourceIdRule);
validator.setBestPracticeWarningLevel(bpWarnings);
validator.setCheckDisplay(displayOption);
@ -393,7 +394,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
public org.hl7.fhir.r5.elementmodel.Element transform(byte[] source, FhirFormat cntType, String mapUri) throws FHIRException, IOException {
List<Base> outputs = new ArrayList<>();
StructureMapUtilities scu = new StructureMapUtilities(context, new TransformSupportServices(outputs, mapLog, context));
org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(source), cntType);
org.hl7.fhir.r5.elementmodel.Element src = Manager.parseSingle(context, new ByteArrayInputStream(source), cntType);
StructureMap map = context.getTransform(mapUri);
if (map == null) throw new Error("Unable to find map " + mapUri + " (Known Maps = " + context.listMapUrls() + ")");
org.hl7.fhir.r5.elementmodel.Element resource = getTargetResourceFromStructureMap(map);
@ -448,14 +449,14 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
public void convert(String source, String output) throws FHIRException, IOException {
Content cnt = igLoader.loadContent(source, "validate", false);
Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
Manager.compose(context, e, new FileOutputStream(output), (output.endsWith(".json") ? FhirFormat.JSON : FhirFormat.XML), OutputStyle.PRETTY, null);
}
public String evaluateFhirPath(String source, String expression) throws FHIRException, IOException {
Content cnt = igLoader.loadContent(source, "validate", false);
FHIRPathEngine fpe = this.getValidator().getFHIRPathEngine();
Element e = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
FHIRPathEngine fpe = this.getValidator(null).getFHIRPathEngine();
Element e = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
ExpressionNode exp = fpe.parse(expression);
return fpe.evaluateToString(new ValidatorHostContext(context, e), e, e, e, exp);
}
@ -490,7 +491,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
context.dropResource(type, id);
}
public InstanceValidator getValidator() {
public InstanceValidator getValidator(FhirFormat format) throws FHIRException, IOException {
InstanceValidator validator = new InstanceValidator(context, null, null);
validator.setHintAboutNonMustSupport(hintAboutNonMustSupport);
validator.setAnyExtensionsAllowed(anyExtensionsAllowed);
@ -512,6 +513,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
validator.getBundleValidationRules().addAll(bundleValidationRules);
validator.getValidationControl().putAll(validationControl);
validator.setQuestionnaireMode(questionnaireMode);
if (format == FhirFormat.SHC) {
igLoader.loadIg(getIgs(), getBinaries(), SHCParser.CURRENT_PACKAGE, true);
}
return validator;
}
@ -617,7 +622,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
public byte[] transformVersion(String source, String targetVer, FhirFormat format, Boolean canDoNative) throws FHIRException, IOException, Exception {
Content cnt = igLoader.loadContent(source, "validate", false);
org.hl7.fhir.r5.elementmodel.Element src = Manager.parse(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
org.hl7.fhir.r5.elementmodel.Element src = Manager.parseSingle(context, new ByteArrayInputStream(cnt.focus), cnt.cntType);
// if the src has a url, we try to use the java code
if ((canDoNative == null && src.hasChild("url")) || (canDoNative != null && canDoNative)) {

View File

@ -235,7 +235,7 @@ public class ValidatorCli {
}
System.out.println("Validating");
if (cliContext.getMode() == EngineMode.SCAN) {
Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(), validator.getIgLoader(), validator.getFhirPathEngine());
Scanner validationScanner = new Scanner(validator.getContext(), validator.getValidator(null), validator.getIgLoader(), validator.getFhirPathEngine());
validationScanner.validateScan(cliContext.getOutput(), cliContext.getSources());
} else {
validationService.validateSources(cliContext, validator);

View File

@ -67,6 +67,7 @@ import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.elementmodel.ParserBase;
import org.hl7.fhir.r5.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy;
import org.hl7.fhir.r5.elementmodel.XmlParser;
import org.hl7.fhir.r5.formats.FormatUtilities;
@ -296,13 +297,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
try {
Element e = new ObjectConverter(context).convert((Resource) item);
setParents(e);
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, e, validationLanguage));
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
} catch (IOException e1) {
throw new FHIRException(e1);
}
} else if (item instanceof Element) {
Element e = (Element) item;
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, e, validationLanguage));
self.validateResource(new ValidatorHostContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage));
} else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
boolean ok = true;
@ -566,16 +567,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
((XmlParser) parser).setAllowXsiLocation(allowXsiLocation);
parser.setupValidation(ValidationPolicy.EVERYTHING, errors);
long t = System.nanoTime();
Element e;
List<NamedElement> list = null;
try {
e = parser.parse(stream);
list = parser.parse(stream);
} catch (IOException e1) {
throw new FHIRException(e1);
}
timeTracker.load(t);
if (e != null)
validate(appContext, errors, e, profiles);
return e;
if (list != null && !list.isEmpty()) {
String url = parser.getImpliedProfile();
if (url != null) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null) {
rule(errors, IssueType.NOTFOUND, "Payload", false, "Implied profile "+url+" not known to validator");
} else {
profiles.add(sd);
}
}
for (NamedElement ne : list) {
validate(appContext, errors, ne.getName(), ne.getElement(), profiles);
}
}
return (list == null || list.isEmpty()) ? null : list.get(0).getElement(); // todo: this is broken, but fixing it really complicates things elsewhere, so we do this for now
}
@Override
@ -602,7 +615,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
throw new FHIRException(e1);
}
timeTracker.load(t);
validate(appContext, errors, e, profiles);
validate(appContext, errors, null, e, profiles);
return e;
}
@ -633,7 +646,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
timeTracker.load(t);
if (e != null) {
validate(appContext, errors, e, profiles);
validate(appContext, errors, null, e, profiles);
}
return e;
}
@ -665,7 +678,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
timeTracker.load(t);
if (e != null)
validate(appContext, errors, e, profiles);
validate(appContext, errors, null, e, profiles);
return e;
}
@ -691,26 +704,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Element e = parser.parse(object);
timeTracker.load(t);
if (e != null)
validate(appContext, errors, e, profiles);
validate(appContext, errors, null, e, profiles);
return e;
}
@Override
public void validate(Object appContext, List<ValidationMessage> errors, Element element) throws FHIRException {
validate(appContext, errors, element, new ArrayList<>());
public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element) throws FHIRException {
validate(appContext, errors, initialPath, element, new ArrayList<>());
}
@Override
public void validate(Object appContext, List<ValidationMessage> errors, Element element, String profile) throws FHIRException {
public void validate(Object appContext, List<ValidationMessage> errors, String initialPath, Element element, String profile) throws FHIRException {
ArrayList<StructureDefinition> profiles = new ArrayList<>();
if (profile != null) {
profiles.add(getSpecifiedProfile(profile));
}
validate(appContext, errors, element, profiles);
validate(appContext, errors, initialPath, element, profiles);
}
@Override
public void validate(Object appContext, List<ValidationMessage> errors, Element element, List<StructureDefinition> profiles) throws FHIRException {
public void validate(Object appContext, List<ValidationMessage> errors, String path, Element element, List<StructureDefinition> profiles) throws FHIRException {
// this is the main entry point; all the other public entry points end up here coming here...
// so the first thing to do is to clear the internal state
fetchCache.clear();
@ -724,14 +737,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime();
if (profiles == null || profiles.isEmpty()) {
validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, element, validationLanguage).resetIds());
validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds());
} else {
for (StructureDefinition defn : profiles) {
validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, element, validationLanguage).resetIds());
validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds());
}
}
if (hintAboutNonMustSupport) {
checkElementUsage(errors, element, new NodeStack(context, element, validationLanguage));
checkElementUsage(errors, element, new NodeStack(context, path, element, validationLanguage));
}
errors.removeAll(messagesToRemove);
timeTracker.overall(t);
@ -3049,7 +3062,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return context;
}
private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve) throws FHIRException {
private List<ElementDefinition> getCriteriaForDiscriminator(String path, ElementDefinition element, String discriminator, StructureDefinition profile, boolean removeResolve, StructureDefinition srcProfile) throws FHIRException {
List<ElementDefinition> elements = new ArrayList<ElementDefinition>();
if ("value".equals(discriminator) && element.hasFixed()) {
elements.add(element);
@ -3075,7 +3088,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
throw new FHIRException(context.formatMessage(I18nConstants.DISCRIMINATOR_BAD_PATH, e.getMessage(), fp), e);
}
long t2 = System.nanoTime();
ed = fpe.evaluateDefinition(expr, profile, element);
ed = fpe.evaluateDefinition(expr, profile, element, srcProfile);
timeTracker.sd(t2);
if (ed != null)
elements.add(ed);
@ -3100,7 +3113,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
expr = fpe.parse(fp);
t2 = System.nanoTime();
ed = fpe.evaluateDefinition(expr, profile, element);
ed = fpe.evaluateDefinition(expr, profile, element, srcProfile);
timeTracker.sd(t2);
if (ed != null)
elements.add(ed);
@ -3387,7 +3400,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
rr.setResource(res.getMatch());
rr.setFocus(res.getMatch());
rr.setExternal(false);
rr.setStack(new NodeStack(context, hostContext, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
rr.setStack(new NodeStack(context, null, hostContext, validationLanguage).push(res.getEntry(), res.getIndex(), res.getEntry().getProperty().getDefinition(),
res.getEntry().getProperty().getDefinition()).push(res.getMatch(), -1,
res.getMatch().getProperty().getDefinition(), res.getMatch().getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+rr.getResource().fhirType()+")");
@ -3550,13 +3563,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* @param ed - the slice for which to test membership
* @param errors
* @param stack
* @param srcProfile
* @return
* @throws DefinitionException
* @throws DefinitionException
* @throws IOException
* @throws FHIRException
*/
private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack) throws DefinitionException, FHIRException {
private boolean sliceMatches(ValidatorHostContext hostContext, Element element, String path, ElementDefinition slicer, ElementDefinition ed, StructureDefinition profile, List<ValidationMessage> errors, List<ValidationMessage> sliceInfo, NodeStack stack, StructureDefinition srcProfile) throws DefinitionException, FHIRException {
if (!slicer.getSlicing().hasDiscriminator())
return false; // cannot validate in this case
@ -3571,7 +3585,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String discriminator = s.getPath();
discriminators.add(discriminator);
List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE);
List<ElementDefinition> criteriaElements = getCriteriaForDiscriminator(path, ed, discriminator, profile, s.getType() == DiscriminatorType.PROFILE, srcProfile);
boolean found = false;
for (ElementDefinition criteriaElement : criteriaElements) {
found = true;
@ -4873,6 +4887,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
int last = -1;
ElementInfo lastei = null;
int lastSlice = -1;
for (ElementInfo ei : children) {
String sliceInfo = "";
@ -4911,13 +4926,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!ToolingExtensions.readBoolExtension(profile, "http://hl7.org/fhir/StructureDefinition/structuredefinition-xml-no-order")) {
boolean ok = (ei.definition == null) || (ei.index >= last) || isXmlAttr;
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getUrl(), ei.getName());
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), ok, I18nConstants.VALIDATION_VAL_PROFILE_OUTOFORDER, profile.getUrl(), ei.getName(), lastei == null ? "(null)" : lastei.getName());
}
if (ei.slice != null && ei.index == last && ei.slice.getSlicing().getOrdered()) {
rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), (ei.definition == null) || (ei.sliceindex >= lastSlice) || isXmlAttr, I18nConstants.VALIDATION_VAL_PROFILE_SLICEORDER, profile.getUrl(), ei.getName());
}
if (ei.definition == null || !isXmlAttr) {
last = ei.index;
lastei = ei;
}
if (ei.slice != null) {
lastSlice = ei.sliceindex;
@ -4950,7 +4966,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
if (nameMatches(ei.getName(), tail(ed.getPath())))
try {
match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack);
match = sliceMatches(hostContext, ei.getElement(), ei.getPath(), slicer, ed, profile, errors, sliceInfo, stack, profile);
if (match) {
ei.slice = slicer;

View File

@ -30,11 +30,11 @@ public class NodeStack {
this.context = context;
}
public NodeStack(IWorkerContext context, Element element, String validationLanguage) {
public NodeStack(IWorkerContext context, String initialPath, Element element, String validationLanguage) {
this.context = context;
ids = new HashMap<>();
this.element = element;
literalPath = element.getPath();
literalPath = (initialPath == null ? "" : initialPath+".") + element.getPath();
workingLang = validationLanguage;
if (!element.getName().equals(element.fhirType())) {
logicalPaths = new ArrayList<>();

View File

@ -1,11 +1,13 @@
package org.hl7.fhir.validation.tests;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -31,6 +33,7 @@ import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.elementmodel.SHCParser;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base;
@ -162,8 +165,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("use-test") && !content.get("use-test").getAsBoolean())
return;
String testCaseContent = TestingUtilities.loadTestResource("validator", JSONUtil.str(content, "file"));
InstanceValidator val = vCurr.getValidator();
byte[] testCaseContent = TestingUtilities.loadTestResource("validator", JSONUtil.str(content, "file")).getBytes(StandardCharsets.UTF_8);
// load and process content
FhirFormat fmt = determineFormat(content, testCaseContent);
InstanceValidator val = vCurr.getValidator(fmt);
val.setWantCheckSnapshotUnchanged(true);
val.getContext().setClientRetryCount(4);
val.setDebug(false);
@ -236,13 +242,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (content.has("security-checks")) {
val.setSecurityChecks(content.get("security-checks").getAsBoolean());
}
if (content.has("logical")==false) {
val.setAssumeValidRestReferences(content.has("assumeValidRestReferences") ? content.get("assumeValidRestReferences").getAsBoolean() : false);
System.out.println(String.format("Start Validating (%d to set up)", (System.nanoTime() - setup) / 1000000));
if (JSONUtil.str(content, "file").endsWith(".json"))
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON);
else
val.validate(null, errors, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML);
val.validate(null, errors, new ByteArrayInputStream(testCaseContent), fmt);
System.out.println(val.reportTimes());
checkOutcomes(errors, content, null, name);
}
@ -281,10 +285,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
val.setAssumeValidRestReferences(profile.has("assumeValidRestReferences") ? profile.get("assumeValidRestReferences").getAsBoolean() : false);
List<ValidationMessage> errorsProfile = new ArrayList<ValidationMessage>();
if (JSONUtil.str(content, "file").endsWith(".json"))
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.JSON, asSdList(sd));
else
val.validate(null, errorsProfile, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), FhirFormat.XML, asSdList(sd));
val.validate(null, errorsProfile, new ByteArrayInputStream(testCaseContent), fmt, asSdList(sd));
System.out.println(val.reportTimes());
checkOutcomes(errorsProfile, profile, filename, name);
}
@ -309,7 +310,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
}
List<ValidationMessage> errorsLogical = new ArrayList<ValidationMessage>();
Element le = val.validate(null, errorsLogical, IOUtils.toInputStream(testCaseContent, Charsets.UTF_8), (name.endsWith(".json")) ? FhirFormat.JSON : FhirFormat.XML);
Element le = val.validate(null, errorsLogical, new ByteArrayInputStream(testCaseContent), fmt);
if (logical.has("expressions")) {
FHIRPathEngine fp = new FHIRPathEngine(val.getContext());
for (JsonElement e : logical.getAsJsonArray("expressions")) {
@ -321,6 +322,11 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
}
}
private FhirFormat determineFormat(JsonObject config, byte[] cnt) throws IOException {
String name = JSONUtil.str(config, "file");
return org.hl7.fhir.validation.ResourceChecker.checkIsResource(vCurr.getContext(), true, cnt, name, !JSONUtil.bool(config, "guess-format"));
}
private List<StructureDefinition> asSdList(StructureDefinition sd) {
List<StructureDefinition> res = new ArrayList<StructureDefinition>();
res.add(sd);
@ -482,16 +488,16 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
if (url.equals("Patient/test")) {
res = new ObjectConverter(TestingUtilities.context(version)).convert(new Patient());
} else if (TestingUtilities.findTestResource("validator", url.replace("/", "-").toLowerCase() + ".json")) {
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parse(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".json"));
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parseSingle(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".json"));
} else if (TestingUtilities.findTestResource("validator", url.replace("/", "-").toLowerCase() + ".xml")) {
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parse(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".xml"));
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("validator", url.replace("/", "-").toLowerCase() + ".xml"));
}
if (res == null && url.contains("/")) {
String tail = url.substring(url.indexOf("/") + 1);
if (TestingUtilities.findTestResource("validator", tail.replace("/", "-").toLowerCase() + ".json")) {
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parse(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".json"));
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.JSON).parseSingle(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".json"));
} else if (TestingUtilities.findTestResource("validator", tail.replace("/", "-").toLowerCase() + ".xml")) {
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parse(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".xml"));
res = Manager.makeParser(TestingUtilities.context(version), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("validator", tail.replace("/", "-").toLowerCase() + ".xml"));
}
}
return res;