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:
parent
72b7300700
commit
342d75b2df
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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"));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue