* fix issue validating # references

* Mark it has an error if a JSON Array is empty

* Don't make wrong error reports for profiling resources in bundles

* * Render binding description in profile tables if it doesn't contain paragraphs
* fix bug with wrong value for contentReference in derived profiles (profiles do not and cannot change the value)

* fix bug with wrong value for contentReference in derived profiles (profiles do not and cannot change the value) (missed testing change)

* * fix bug not recognising some content as xml or json

* improved markdown support in table generator

* * fix bug checking unfixed values for HumanName patterns
* fix bug checking patterns (missed in some circumstances)
* fix bug checking type of resources in bundles
* improve messages around cardinality errors in profiles

* add parameter -html-output for enhanced presentation of slicing information (issue #283)
This commit is contained in:
Grahame Grieve 2020-10-30 00:17:24 +11:00 committed by GitHub
parent e1ecd06282
commit c5255a0f80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 541 additions and 70 deletions

View File

@ -0,0 +1,12 @@
Validator Changes:
* Mark it has an error if a JSON Array is empty
* Don't make wrong error reports for profiling resources in bundles
* fix bug checking unfixed values for HumanName patterns
* fix bug checking patterns (missed in some circumstances)
* fix bug checking type of resources in bundles
* improve messages around cardinality errors in profiles
* add parameter -html-output for enhanced presentation of slicing information
Other code changes:
* Render binding description in profile tables if it doesn't contain paragraphs
* fix bug with wrong value for contentReference in derived profiles (profiles do not and cannot change the value)

View File

@ -111,6 +111,7 @@ import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
import org.hl7.fhir.r5.utils.formats.CSVWriter;
import org.hl7.fhir.r5.utils.formats.XLSXWriter;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
@ -362,9 +363,28 @@ public class ProfileUtilities extends TranslatingUtilities {
public List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
if (element.getContentReference()!=null) {
for (ElementDefinition e : profile.getSnapshot().getElement()) {
if (element.getContentReference().equals("#"+e.getId()))
if (element.getContentReference() != null) {
List<ElementDefinition> list = null;
String id = null;
if (element.getContentReference().startsWith("#")) {
// internal reference
id = element.getContentReference().substring(1);
list = profile.getSnapshot().getElement();
} else if (element.getContentReference().contains("#")) {
// external reference
String ref = element.getContentReference();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")));
if (sd == null) {
throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
}
list = sd.getSnapshot().getElement();
id = ref.substring(ref.indexOf("#")+1);
} else {
throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
}
for (ElementDefinition e : list) {
if (id.equals(e.getId()))
return getChildMap(profile, e);
}
throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath()));
@ -3155,6 +3175,10 @@ public class ProfileUtilities extends TranslatingUtilities {
c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));
c.getPieces().add(gen.new Piece(null, ")", null));
}
if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(ved.getBinding().getDescription());
}
}
c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
r.getCells().add(c);
@ -4282,6 +4306,10 @@ public class ProfileUtilities extends TranslatingUtilities {
c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold")));
c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
}
if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(binding.getDescription());
}
}
for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) {
@ -4300,7 +4328,6 @@ public class ProfileUtilities extends TranslatingUtilities {
// don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
}
}
if (definition.hasFixed()) {
if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
@ -4594,6 +4621,10 @@ public class ProfileUtilities extends TranslatingUtilities {
c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition()))); c.getPieces().add(gen.new Piece(null, ")", null));
}
if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
c.getPieces().add(gen.new Piece(null, ": ", null));
c.addMarkdownNoPara(binding.getDescription());
}
}
for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
@ -5338,12 +5369,12 @@ public class ProfileUtilities extends TranslatingUtilities {
if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
if (!sd.hasDifferential())
sd.setDifferential(new StructureDefinitionDifferentialComponent());
generateIds(sd.getDifferential().getElement(), sd.getUrl());
generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType());
}
if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
if (!sd.hasSnapshot())
sd.setSnapshot(new StructureDefinitionSnapshotComponent());
generateIds(sd.getSnapshot().getElement(), sd.getUrl());
generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType());
}
}
@ -5388,11 +5419,10 @@ public class ProfileUtilities extends TranslatingUtilities {
}
private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException {
private void generateIds(List<ElementDefinition> list, String name, String type) throws DefinitionException {
if (list.isEmpty())
return;
Map<String, String> idMap = new HashMap<String, String>();
Map<String, String> idList = new HashMap<String, String>();
SliceList sliceInfo = new SliceList();
@ -5420,7 +5450,6 @@ public class ProfileUtilities extends TranslatingUtilities {
}
}
String bs = b.toString();
idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
ed.setId(bs);
if (idList.containsKey(bs)) {
if (exception || messages == null) {
@ -5429,11 +5458,9 @@ public class ProfileUtilities extends TranslatingUtilities {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
}
idList.put(bs, ed.getPath());
if (ed.hasContentReference()) {
String s = ed.getContentReference().substring(1);
if (idMap.containsKey(s))
ed.setContentReference("#"+idMap.get(s));
if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
String s = ed.getContentReference();
ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s);
}
}
// second path - fix up any broken path based id references

View File

@ -208,6 +208,9 @@ public class JsonParser extends ParserBase {
JsonElement e = object.get(name);
if (property.isList() && (e instanceof JsonArray)) {
JsonArray arr = (JsonArray) e;
if (arr.size() == 0) {
logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR);
}
int c = 0;
for (JsonElement am : arr) {
parseChildComplexInstance(npath+"["+c+"]", object, element, property, name, am);

View File

@ -46,6 +46,7 @@ public class OperationOutcomeUtilities {
public static OperationOutcomeIssueComponent convertToIssue(ValidationMessage message, OperationOutcome op) {
OperationOutcomeIssueComponent issue = new OperationOutcome.OperationOutcomeIssueComponent();
issue.setUserData("source.vm", message);
issue.setCode(convert(message.getType()));
if (message.getLocation() != null) {

View File

@ -104,6 +104,8 @@ public class ProfileUtilitiesTests {
f.setComment(null);
b.setDefinition(null);
f.setDefinition(null);
b.setContentReference(null);
f.setContentReference(null);
ok = Base.compareDeep(b, f, true);
}
}

View File

@ -78,5 +78,10 @@ public class MarkDownProcessor {
html = html.replace("<table>", "<table class=\"grid\">");
return html;
}
public static boolean isSimpleMarkdown(String description) {
return !description.contains("\n");
}
}

View File

@ -12,6 +12,7 @@ public class I18nConstants {
public static final String ALL_OBSERVATIONS_SHOULD_HAVE_A_PERFORMER = "All_observations_should_have_a_performer";
public static final String ALL_OBSERVATIONS_SHOULD_HAVE_A_SUBJECT = "All_observations_should_have_a_subject";
public static final String ALL_OK = "ALL_OK";
public static final String ARRAY_CANNOT_BE_EMPTY = "ARRAY_CANNOT_BE_EMPTY";
public static final String ATTEMPT_TO_A_SLICE_AN_ELEMENT_THAT_DOES_NOT_REPEAT__FROM__IN_ = "Attempt_to_a_slice_an_element_that_does_not_repeat__from__in_";
public static final String ATTEMPT_TO_REPLACE_ELEMENT_NAME_FOR_A_NONCHOICE_TYPE = "Attempt_to_replace_element_name_for_a_nonchoice_type";
public static final String ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED = "Attempt_to_use_a_snapshot_on_profile__as__before_it_is_generated";

View File

@ -240,6 +240,7 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
pieces.add(piece);
return this;
}
public Cell addMarkdown(String md) {
try {
Parser parser = Parser.builder().build();
@ -253,6 +254,19 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
return this;
}
public Cell addMarkdownNoPara(String md) {
try {
Parser parser = Parser.builder().build();
Node document = parser.parse(md);
HtmlRenderer renderer = HtmlRenderer.builder().escapeHtml(true).build();
String html = renderer.render(document);
pieces.addAll(htmlToParagraphPieces(html));
} catch (Exception e) {
e.printStackTrace();
}
return this;
}
private List<Piece> htmlToParagraphPieces(String html) {
List<Piece> myPieces = new ArrayList<Piece>();
try {

View File

@ -209,11 +209,12 @@ Validation_BUNDLE_Message = The first entry in a message must be a MessageHeader
Validation_VAL_Content_Unknown = Unrecognised Content {0}
Validation_VAL_NoType = Unknown type {0}
Validation_VAL_Profile_MatchMultiple = Profile {0}, Element matches more than one slice - {1}, {2}
Validation_VAL_Profile_Maximum = {0}: max allowed = {1}, but found {2}
Validation_VAL_Profile_Minimum = {0}: minimum required = {1}, but only found {2}
// for the next 4 messages, the available parameters are: 0: profile url, 1: ed.path, 2: ed.id, 3: ed.sliceName, 4: ed.label, 5: element.path, 6: ed.min and optionally 7: actual count
Validation_VAL_Profile_Maximum = {2}: max allowed = {6}, but found {7} (from {0})
Validation_VAL_Profile_Minimum = {2}: minimum required = {6}, but only found {7} (from {0})
Validation_VAL_Profile_NoCheckMax = {2}: Unable to check max allowed ({1}) due to lack of slicing validation (from {0})
Validation_VAL_Profile_NoCheckMin = {2}: Unable to check minimum required ({1}) due to lack of slicing validation (from {0})
Validation_VAL_Profile_MultipleMatches = Found multiple matching profiles among choices: {0}
Validation_VAL_Profile_NoCheckMax = {0}: Unable to check max allowed ({1}) due to lack of slicing validation
Validation_VAL_Profile_NoCheckMin = {0}'': Unable to check minimum required ({1}) due to lack of slicing validation
Validation_VAL_Profile_NoDefinition = No definition found for resource type ''{0}''
Validation_VAL_Profile_NoMatch = Unable to find matching profile among choices: {0}
Validation_VAL_Profile_NoSnapshot = StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided
@ -617,4 +618,5 @@ SD_ED_TYPE_PROFILE_NOTYPE = Found profile {0}, but unable to determine the type
SD_ED_TYPE_PROFILE_WRONG = Profile {0} is for type {1}, but this element has type {2}
TERMINOLOGY_TX_NOSVC_BOUND_REQ = Could not confirm that the codes provided are from the required value set {0} because there is no terminology service
TERMINOLOGY_TX_NOSVC_BOUND_EXT = Could not confirm that the codes provided are from the extensible value set {0} because there is no terminology service
ARRAY_CANNOT_BE_EMPTY = Array cannot be empty - the property should not be present if it has no values

View File

@ -29,6 +29,10 @@ import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.convertors.VersionConvertorAdvisor50;
import org.hl7.fhir.convertors.VersionConvertor_10_30;
@ -56,6 +60,7 @@ import org.hl7.fhir.r5.context.SimpleWorkerContext;
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.ParserBase.ValidationPolicy;
import org.hl7.fhir.r5.elementmodel.ObjectConverter;
import org.hl7.fhir.r5.formats.FormatUtilities;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
@ -98,19 +103,23 @@ import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.npm.ToolsVersion;
import org.hl7.fhir.utilities.turtle.Turtle;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.model.ScanOutputItem;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
import org.hl7.fhir.validation.cli.utils.*;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
@ -187,6 +196,50 @@ POSSIBILITY OF SUCH DAMAGE.
*/
public class ValidationEngine implements IValidatorResourceFetcher, IPackageInstaller {
public class ValidationRecord {
private String location;
private List<ValidationMessage> messages;
int err = 0;
int warn = 0;
int info = 0;
public ValidationRecord(String location, List<ValidationMessage> messages) {
this.location = location;
this.messages = messages;
for (ValidationMessage vm : messages) {
if (vm.getLevel().equals(ValidationMessage.IssueSeverity.FATAL)||vm.getLevel().equals(ValidationMessage.IssueSeverity.ERROR))
err++;
else if (vm.getLevel().equals(ValidationMessage.IssueSeverity.WARNING))
warn++;
else if (!vm.isSignpost()) {
info++;
}
}
}
public String getLocation() {
return location;
}
public List<ValidationMessage> getMessages() {
return messages;
}
public int getErr() {
return err;
}
public int getWarn() {
return warn;
}
public int getInfo() {
return info;
}
}
public class TransformSupportServices implements ITransformerServices {
private List<Base> outputs;
@ -538,7 +591,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
throw new FHIRException("Unable to fetch content from "+src+" ("+errors.toString()+")");
}
FhirFormat fmt = checkIsResource(cnt, src);
FhirFormat fmt = checkFormat(cnt, src);
if (fmt != null) {
Map<String, byte[]> res = new HashMap<String, byte[]>();
res.put(Utilities.changeFileExt(src, "."+fmt.getExtension()), cnt);
@ -809,33 +862,99 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
this.noInvariantChecks = value;
}
private FhirFormat checkFormat(byte[] cnt, String filename) {
System.out.println(" ..Detect format for "+filename);
try {
JsonTrackingParser.parseJson(cnt);
return FhirFormat.JSON;
} catch (Exception e) {
if (debug) {
System.out.println("Not JSON: "+e.getMessage());
}
}
try {
parseXml(cnt);
return FhirFormat.XML;
} catch (Exception e) {
if (debug) {
System.out.println("Not XML: "+e.getMessage());
}
}
try {
new Turtle().parse(TextFile.bytesToString(cnt));
return FhirFormat.TURTLE;
} catch (Exception e) {
if (debug) {
System.out.println("Not Turtle: "+e.getMessage());
}
}
try {
new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(cnt), null);
return FhirFormat.TEXT;
} catch (Exception e) {
if (debug) {
System.out.println("Not Text: "+e.getMessage());
}
}
if (debug)
System.out.println(" .. not a resource: "+filename);
return null;
}
private FhirFormat checkIsResource(byte[] cnt, String filename) {
System.out.println(" ..Detect format for "+filename);
try {
Manager.parse(context, new ByteArrayInputStream(cnt), FhirFormat.JSON);
return FhirFormat.JSON;
} catch (Exception e) {
if (debug) {
System.out.println("Not JSON: "+e.getMessage());
}
}
try {
Manager.parse(context, new ByteArrayInputStream(cnt),FhirFormat.XML);
parseXml(cnt);
return FhirFormat.XML;
} catch (Exception e) {
if (debug) {
System.out.println("Not XML: "+e.getMessage());
}
}
try {
Manager.parse(context, new ByteArrayInputStream(cnt),FhirFormat.TURTLE);
return FhirFormat.TURTLE;
} catch (Exception e) {
if (debug) {
System.out.println("Not Turtle: "+e.getMessage());
}
}
try {
new StructureMapUtilities(context, null, null).parse(TextFile.bytesToString(cnt), null);
return FhirFormat.TEXT;
} catch (Exception e) {
if (debug) {
System.out.println("Not Text: "+e.getMessage());
}
}
if (debug)
System.out.println(" .. not a resource: "+filename);
return null;
}
private Document parseXml(byte[] cnt) throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// xxe protection
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new ByteArrayInputStream(cnt));
}
private FhirFormat checkIsResource(String path) throws IOException {
String ext = Utilities.getFileExtension(path);
if (Utilities.existsInList(ext, "xml"))
@ -1097,7 +1216,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
public OperationOutcome validate(String source, List<String> profiles) throws FHIRException, IOException {
List<String> l = new ArrayList<String>();
l.add(source);
return (OperationOutcome)validate(l, profiles);
return (OperationOutcome)validate(l, profiles, null);
}
public List<ScanOutputItem> validateScan(List<String> sources, Set<String> guides) throws FHIRException, IOException, EOperationOutcome {
@ -1193,7 +1312,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
}
}
public Resource validate(List<String> sources, List<String> profiles) throws FHIRException, IOException {
public Resource validate(List<String> sources, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException {
if (profiles.size() > 0) {
System.out.println(" Profiles: "+profiles);
}
@ -1207,7 +1326,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
System.out.print(" Validate " + ref);
Content cnt = loadContent(ref, "validate", false);
try {
OperationOutcome outcome = validate(ref, cnt.focus, cnt.cntType, profiles);
OperationOutcome outcome = validate(ref, cnt.focus, cnt.cntType, profiles, record);
ToolingExtensions.addStringExtension(outcome, ToolingExtensions.EXT_OO_FILE, ref);
System.out.println(" " + context.clock().milestone());
results.addEntry().setResource(outcome);
@ -1281,7 +1400,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
}
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles) throws FHIRException, IOException, EOperationOutcome, SAXException {
public OperationOutcome validate(String location, byte[] source, FhirFormat cntType, List<String> profiles, List<ValidationRecord> record) throws FHIRException, IOException, EOperationOutcome, SAXException {
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();
if (doNative) {
SchemaValidator.validateSchema(location, cntType, messages);
@ -1291,6 +1410,9 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
if (showTimes) {
System.out.println(location+": "+validator.reportTimes());
}
if (record != null) {
record.add(new ValidationRecord(location, messages));
}
return messagesToOutcome(messages);
}

View File

@ -41,6 +41,8 @@ public class CliContext {
private String map = null;
@JsonProperty("output")
private String output = null;
@JsonProperty("htmlOutput")
private String htmlOutput = null;
@JsonProperty("txServer")
private String txServer = "http://tx.fhir.org";
@JsonProperty("sv")
@ -252,6 +254,17 @@ public class CliContext {
return this;
}
@JsonProperty("output")
public String getHtmlOutput() {
return htmlOutput;
}
@JsonProperty("output")
public CliContext setHtmlOutput(String htmlOutput) {
this.htmlOutput = htmlOutput;
return this;
}
@JsonProperty("canDoNative")
public boolean getCanDoNative() {
return canDoNative;
@ -472,6 +485,7 @@ public class CliContext {
noExtensibleBindingMessages == that.noExtensibleBindingMessages &&
Objects.equals(map, that.map) &&
Objects.equals(output, that.output) &&
Objects.equals(htmlOutput, that.htmlOutput) &&
Objects.equals(txServer, that.txServer) &&
Objects.equals(sv, that.sv) &&
Objects.equals(txLog, that.txLog) &&
@ -493,7 +507,7 @@ public class CliContext {
@Override
public int hashCode() {
return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, map, output, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes);
return Objects.hash(doNative, anyExtensionsAllowed, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching, noExtensibleBindingMessages, map, output, htmlOutput, txServer, sv, txLog, mapLog, lang, fhirpath, snomedCT, targetVer, igs, questionnaireMode, profiles, sources, mode, locale, locations, crumbTrails, showTimes);
}
@Override
@ -510,6 +524,7 @@ public class CliContext {
", noExtensibleBindingMessages=" + noExtensibleBindingMessages +
", map='" + map + '\'' +
", output='" + output + '\'' +
", htmlOutput='" + htmlOutput + '\'' +
", txServer='" + txServer + '\'' +
", sv='" + sv + '\'' +
", txLog='" + txLog + '\'' +

View File

@ -0,0 +1,181 @@
package org.hl7.fhir.validation.cli.services;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.utils.VersionUtil;
public class HTMLOutputGenerator {
private List<ValidationRecord> records;
public HTMLOutputGenerator(List<ValidationRecord> records) {
super();
this.records = records;
}
public String generate(long time) {
StringBuilder b = new StringBuilder();
b.append(genHeader(time));
int i = 0;
for (ValidationRecord f : records) {
i++;
b.append(genSummaryRow(i, f));
}
b.append("</table>\r\n");
i = 0;
int id = 0;
for (ValidationRecord f : records) {
i++;
b.append(genStart(i, f));
if (f.getMessages().size() > 0) {
b.append(
" <table class=\"grid\">\r\n"+
" <tr>\r\n"+
" <td><b>Path</b></td><td><b>Severity</b></td><td><b>Message</b></td>\r\n"+
" </tr>\r\n");
for (ValidationMessage vm : f.getMessages()) {
id++;
b.append(genDetails(vm, "m"+id));
}
b.append("</table>\r\n");
} else {
b.append("<p>No Issues detected</p>\r\n");
}
}
return b.toString();
}
private String genHeader(long time) {
int err = 0;
int warn = 0;
int info = 0;
for (ValidationRecord f : records) {
err = err + f.getErr();
warn = warn + f.getWarn();
info = info + f.getInfo();
}
return
"<!DOCTYPE HTML>\r\n"+
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\r\n"+
"<head>\r\n"+
" <title>Validation Results</title>\r\n"+
" <link href=\"http://hl7.org/fhir/fhir.css\" rel=\"stylesheet\"/>\r\n"+
" <style>\r\n"+
" span.flip { background-color: #4CAF50; color: white; border: solid 1px #a6d8a8; padding: 2px }\r\n"+
" </style>\r\n"+
" <script>\r\n"+
" function flip(id) {\r\n"+
" var span = document.getElementById('s'+id);\r\n"+
" var div = document.getElementById(id);\r\n"+
" if (document.getElementById('s'+id).innerHTML == 'Show Reasoning') {\r\n"+
" div.style.display = 'block';\r\n"+
" span.innerHTML = 'Hide Reasoning';\r\n"+
" } else {\r\n"+
" div.style.display = 'none';\r\n"+
" span.innerHTML = 'Show Reasoning';\r\n"+
" }\r\n"+
" }\r\n"+
" </script>\r\n"+
"</head>\r\n"+
"<body style=\"margin: 20px; background-color: #ffffff\">\r\n"+
" <h1>Validation Results</h1>\r\n"+
" <p>"+err+" "+Utilities.pluralize("error", err)+", "+warn+" "+Utilities.pluralize("warning", warn)+", "+info+" "+Utilities.pluralize("hint", info)+". Generated "+now()+" by Validator "+VersionUtil.getVersionString()+" ("+time+"ms)</p>\r\n"+
" <table class=\"grid\">\r\n"+
" <tr>\r\n"+
" <td><b>Filename</b></td><td><b>Errors</b></td><td><b>Warnings</b></td><td><b>Hints</b></td>\r\n"+
" </tr>\r\n";
}
private String now() {
return DateFormat.getDateTimeInstance().format(new Date());
}
private String genSummaryRow(int i, ValidationRecord rec) {
String color = colorForLevel(IssueSeverity.ERROR, false);
if (rec.getErr() == 0) {
color = "#EFFFEF";
}
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><a href=\"#;"+i+"\"><b>"+Utilities.escapeXml(rec.getLocation())+"</b></a></td><td><b>"+rec.getErr()+"</b></td><td><b>"+rec.getWarn()+"</b></td><td><b>"+rec.getInfo()+"</b></td>\r\n"+
" </tr>\r\n";
}
private String genStart(int i, ValidationRecord f) {
String xlink = Utilities.isAbsoluteUrl(f.getLocation()) ? f.getLocation() : "file:"+f.getLocation();
return
"<hr/>\r\n"+
"<a name=\"l"+i+"\"> </a>\r\n"+
"<h2><a href=\""+xlink+"\">"+Utilities.escapeXml(f.getLocation())+"</a></h2>\r\n";
}
private String genDetails(ValidationMessage vm, String id) {
String path = vm.getLocation() == null ? "" : vm.getLocation()+ lineCol(vm);
String level = vm.isSlicingHint() ? "Slicing Information" : vm.isSignpost() ? "Process Info" : vm.getLevel().toCode();
String color = colorForLevel(vm.getLevel(), vm.isSignpost());
String mid = vm.getMessageId();
String msg = vm.getHtml();
String msgdetails = vm.isSlicingHint() ? vm.getSliceHtml() : vm.getHtml();
if (vm.isSlicingHint()) {
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><b>"+path+"</b></td><td><b>"+level+"</b></td><td><b>"+msg+"</b> <span id=\"s"+id+"\" class=\"flip\" onclick=\"flip('"+id+"')\">Show Reasoning</span><div id=\""+id+"\" style=\"display: none\"><p>&nbsp;</p>"+msgdetails+"</div></td>\r\n"+
" </tr>\r\n";
} else {
return
" <tr style=\"background-color: "+color+"\">\r\n"+
" <td><b>"+path+"</b></td><td><b>"+level+"</b></td><td title=\""+mid+"\"><b>"+msg+"</b></td>\r\n"+
" </tr>\r\n";
}
}
private String lineCol(ValidationMessage vm) {
return vm.getLine() > 0 ? " (l"+vm.getLine()+"/c"+vm.getCol()+")" : "";
}
private String colorForLevel(IssueSeverity level, boolean signpost) {
if (signpost) {
return "#d6feff";
}
switch (level) {
case ERROR:
return "#ffcccc";
case FATAL:
return "#ff9999";
case WARNING:
return "#ffebcc";
default: // INFORMATION:
return "#ffffe6";
}
}
private String halfColorForLevel(IssueSeverity level, boolean signpost) {
if (signpost) {
return "#e3feff";
}
switch (level) {
case ERROR:
return "#ffeeee";
case FATAL:
return "#ffcccc";
case WARNING:
return "#fff4ee";
default: // INFORMATION:
return "#fffff2";
}
}
}

View File

@ -27,6 +27,7 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.ValidationEngine.ValidationRecord;
import org.hl7.fhir.validation.cli.model.*;
import org.hl7.fhir.validation.cli.utils.EngineMode;
import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
@ -69,7 +70,9 @@ public class ValidationService {
}
public static void validateSources(CliContext cliContext, ValidationEngine validator) throws Exception {
Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles());
long start = System.currentTimeMillis();
List<ValidationRecord> records = new ArrayList<>();
Resource r = validator.validate(cliContext.getSources(), cliContext.getProfiles(), records);
int ec = 0;
System.out.println("Done. "+validator.getContext().clock().report());
System.out.println();
@ -92,6 +95,11 @@ public class ValidationService {
x.compose(s, r);
s.close();
}
if (cliContext.getHtmlOutput() != null) {
String html = new HTMLOutputGenerator(records).generate(System.currentTimeMillis()-start);
TextFile.stringToFile(html, cliContext.getHtmlOutput());
System.out.println("HTML Summary in "+cliContext.getHtmlOutput());
}
System.exit(ec > 0 ? 1 : 0);
}
@ -288,10 +296,12 @@ public class ValidationService {
System.out.println("Scanning for versions (no -version parameter):");
VersionSourceInformation versions = ValidationService.scanForVersions(cliContext);
for (String s : versions.getReport()) {
System.out.println(" " + s);
if (!s.equals("(nothing found)")) {
System.out.println(" " + s);
}
}
if (versions.isEmpty()) {
System.out.println("-> Using Default version '" + VersionUtilities.CURRENT_VERSION + "'");
System.out.println(" No Version Info found: Using Default version '" + VersionUtilities.CURRENT_VERSION + "'");
return "current";
}
if (versions.size() == 1) {

View File

@ -12,6 +12,7 @@ public class Params {
public static final String VERSION = "-version";
public static final String OUTPUT = "-output";
public static final String HTML_OUTPUT = "-html-output";
public static final String PROXY = "-proxy";
public static final String PROFILE = "-profile";
public static final String BUNDLE = "-bundle";
@ -93,6 +94,11 @@ public class Params {
throw new Error("Specified -output without indicating output file");
else
cliContext.setOutput(args[++i]);
} else if (args[i].equals(HTML_OUTPUT)) {
if (i + 1 == args.length)
throw new Error("Specified -html-output without indicating output file");
else
cliContext.setHtmlOutput(args[++i]);
} else if (args[i].equals(PROXY)) {
i++; // ignore next parameter
} else if (args[i].equals(PROFILE)) {

View File

@ -961,7 +961,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, codings.size() == fixed.getCoding().size(), I18nConstants.TERMINOLOGY_TX_CODING_COUNT, Integer.toString(fixed.getCoding().size()), Integer.toString(codings.size()))) {
for (int i = 0; i < codings.size(); i++)
checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus);
checkFixedValue(errors, path + ".coding", codings.get(i), fixed.getCoding().get(i), fixedSource, "coding", focus, false);
}
}
}
@ -1748,10 +1748,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return b.toString();
}
private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent) {
checkFixedValue(errors, path, focus, fixed, fixedSource, propName, parent, false);
}
@SuppressWarnings("rawtypes")
private void checkFixedValue(List<ValidationMessage> errors, String path, Element focus, org.hl7.fhir.r5.model.Element fixed, String fixedSource, String propName, Element parent, boolean pattern) {
if ((fixed == null || fixed.isEmpty()) && focus == null) {
@ -1829,7 +1825,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
for (Extension e : fixed.getExtension()) {
Element ex = getExtensionByUrl(extensions, e.getUrl());
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, ex != null, I18nConstants.EXTENSION_EXT_COUNT_NOTFOUND, e.getUrl())) {
checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"));
checkFixedValue(errors, path, ex.getNamedChild("extension").getNamedChild("value"), e.getValue(), fixedSource, "extension.value", ex.getNamedChild("extension"), false);
}
}
}
@ -1842,25 +1838,33 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkFixedValue(errors, path + ".period", focus.getNamedChild("period"), fixed.getPeriod(), fixedSource, "period", focus, pattern);
List<Element> parts = new ArrayList<Element>();
focus.getNamedChildren("family", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_FAMILY, (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern);
if (!pattern || fixed.hasFamily()) {
focus.getNamedChildren("family", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() > 0 == fixed.hasFamily(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_FAMILY, (fixed.hasFamily() ? "1" : "0"), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".family", parts.get(i), fixed.getFamilyElement(), fixedSource, "family", focus, pattern);
}
}
focus.getNamedChildren("given", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_GIVEN, Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern);
if (!pattern || fixed.hasGiven()) {
focus.getNamedChildren("given", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getGiven().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_GIVEN, Integer.toString(fixed.getGiven().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".given", parts.get(i), fixed.getGiven().get(i), fixedSource, "given", focus, pattern);
}
}
focus.getNamedChildren("prefix", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_PREFIX, Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern);
if (!pattern || fixed.hasPrefix()) {
focus.getNamedChildren("prefix", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getPrefix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_PREFIX, Integer.toString(fixed.getPrefix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".prefix", parts.get(i), fixed.getPrefix().get(i), fixedSource, "prefix", focus, pattern);
}
}
focus.getNamedChildren("suffix", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_SUFFIX, Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern);
if (!pattern || fixed.hasSuffix()) {
focus.getNamedChildren("suffix", parts);
if (rule(errors, IssueType.VALUE, focus.line(), focus.col(), path, parts.size() == fixed.getSuffix().size(), I18nConstants.FIXED_TYPE_CHECKS_DT_NAME_SUFFIX, Integer.toString(fixed.getSuffix().size()), Integer.toString(parts.size()))) {
for (int i = 0; i < parts.size(); i++)
checkFixedValue(errors, path + ".suffix", parts.get(i), fixed.getSuffix().get(i), fixedSource, "suffix", focus, pattern);
}
}
}
@ -3054,9 +3058,22 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// work back through the parent list.
// really, there should only be one level for this (contained resources cannot contain
// contained resources), but we'll leave that to some other code to worry about
boolean wasContained = false;
while (stack != null && stack.getElement() != null) {
if (stack.getElement().getProperty().isResource()) {
// ok, we'll try to find the contained reference
if (ref.equals("#") && stack.getElement().getSpecial() != SpecialElement.CONTAINED && wasContained) {
ResolvedReference rr = new ResolvedReference();
rr.setResource(stack.getElement());
rr.setFocus(stack.getElement());
rr.setExternal(false);
rr.setStack(stack.push(stack.getElement(), -1, stack.getElement().getProperty().getDefinition(), stack.getElement().getProperty().getDefinition()));
rr.getStack().qualifyPath(".ofType("+stack.getElement().fhirType()+")");
return rr;
}
if (stack.getElement().getSpecial() == SpecialElement.CONTAINED) {
wasContained = true;
}
IndexedElement res = getContainedById(stack.getElement(), ref.substring(1));
if (res != null) {
ResolvedReference rr = new ResolvedReference();
@ -4069,8 +4086,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
// check type invariants
checkInvariants(hostContext, errors, profile, definition, resource, element, stack, false);
if (definition.getFixed() != null)
checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null);
if (definition.getFixed() != null) {
checkFixedValue(errors, stack.getLiteralPath(), element, definition.getFixed(), profile.getUrl(), definition.getSliceName(), null, false);
}
if (definition.getPattern() != null) {
checkFixedValue(errors, stack.getLiteralPath(), element, definition.getPattern(), profile.getUrl(), definition.getSliceName(), null, true);
}
// get the list of direct defined children, including slices
List<ElementDefinition> childDefinitions = profileUtilities.getChildMap(profile, definition);
@ -4136,13 +4157,20 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Element resource, Element element, String actualType, NodeStack stack, boolean inCodeableConcept, boolean checkDisplayInContext, ElementInfo ei, String extensionUrl)
throws FHIRException, DefinitionException {
if (debug && ei.definition != null && ei.slice != null) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against both "+ei.definition.getId()+" and "+ei.slice.getId());
}
if (ei.definition != null) {
checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei,
extensionUrl, ei.definition, false);
if (debug) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against defn "+ei.definition.getId());
}
checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.definition, false);
}
if (ei.slice != null) {
checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei,
extensionUrl, ei.slice, true);
if (debug) {
System.out.println(Utilities.padLeft("", ' ', stack.depth())+ "Check "+ei.getPath()+" against slice "+ei.slice.getId());
}
checkChildByDefinition(hostContext, errors, profile, definition, resource, element, actualType, stack, inCodeableConcept, checkDisplayInContext, ei, extensionUrl, ei.slice, true);
}
}
@ -4186,11 +4214,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (checkDefn.getType().size() > 1) {
String prefix = tail(checkDefn.getPath());
assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") : prefix;
assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB();
if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR))
if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
type = ei.getElement().getType();
else {
} else if (ei.getElement().isResource()) {
type = ei.getElement().fhirType();
} else {
prefix = prefix.substring(0, prefix.length() - 3);
for (TypeRefComponent t : checkDefn.getType())
if ((prefix + Utilities.capitalize(t.getWorkingCode())).equals(ei.getName())) {
@ -4251,7 +4281,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkPrimitive(hostContext, errors, ei.getPath(), type, checkDefn, ei.getElement(), profile, stack);
} else {
if (checkDefn.hasFixed()) {
checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getUrl(), checkDefn.getSliceName(), null);
checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getFixed(), profile.getUrl(), checkDefn.getSliceName(), null, false);
}
if (checkDefn.hasPattern()) {
checkFixedValue(errors, ei.getPath(), ei.getElement(), checkDefn.getPattern(), profile.getUrl(), checkDefn.getSliceName(), null, true);
@ -4386,6 +4416,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
private boolean isResourceAndTypes(ElementDefinition ed) {
if (!Utilities.existsInList(ed.getBase().getPath(), "Bundle.entry.resource", "Bundle.entry.response.outcome", "DomainResource.contained", "Parameters.parameter.resource", "Parameters.parameter.part.resource")) {
return false;
}
for (TypeRefComponent tr : ed.getType()) {
if (!isResource(tr.getCode())) {
return false;
}
}
return true;
}
private boolean isResource(String type) {
StructureDefinition sd = context.fetchTypeDefinition(type);
return sd != null && sd.getKind().equals(StructureDefinitionKind.RESOURCE);
@ -4476,18 +4518,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
}
}
String location = "Profile " + profile.getUrl() + ", Element '" + stack.getLiteralPath() + "." + tail(ed.getPath()) + (ed.hasSliceName() ? "[" + ed.getSliceName() + (ed.hasLabel() ? " (" + ed.getLabel() + ")" : "") + "]" : "") + "'";
if (ed.getMin() > 0) {
if (problematicPaths.contains(ed.getPath()))
hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, location, Integer.toString(ed.getMin()));
hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin()));
else
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, location, Integer.toString(ed.getMin()), Integer.toString(count));
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin()), Integer.toString(count));
}
if (ed.hasMax() && !ed.getMax().equals("*")) {
if (problematicPaths.contains(ed.getPath()))
hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMAX, location, ed.getMax());
else
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), I18nConstants.VALIDATION_VAL_PROFILE_MAXIMUM, location, ed.getMax(), Integer.toString(count));
hint(errors, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count <= Integer.parseInt(ed.getMax()), I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMAX, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax());
else if (count > Integer.parseInt(ed.getMax())) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MAXIMUM, profile.getUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), ed.getMax(), Integer.toString(count));
}
}
}
}
@ -4863,7 +4905,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean valueMatchesCriteria(Element value, ElementDefinition criteria, StructureDefinition profile) throws FHIRException {
if (criteria.hasFixed()) {
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getUrl(), "value", null);
checkFixedValue(msgs, "{virtual}", value, criteria.getFixed(), profile.getUrl(), "value", null, false);
return msgs.size() == 0;
} else if (criteria.hasBinding() && criteria.getBinding().getStrength() == BindingStrength.REQUIRED && criteria.getBinding().hasValueSet()) {
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_SLICE_MATCHING__SLICE_MATCHING_BY_VALUE_SET_NOT_DONE));
@ -4983,6 +5025,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if ("(component.empty() and related.empty()) implies (dataAbsentReason or value)".equals(expr))
return "(component.empty() and related.empty()) implies (dataAbsentReason.exists() or value.exists())";
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids'))";
}
if ("reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))".equals(expr)) {
return "(reference = '#') or reference.startsWith('#').not() or (reference.substring(1).trace('url') in %resource.contained.id.trace('ids'))";
}
if ("".equals(expr))
return "";
return expr;

View File

@ -165,11 +165,25 @@ public class StructureDefinitionValidator extends BaseValidator {
if (t == null) {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_NOTYPE, p);
} else {
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), code.equals(t), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code);
rule(errors, IssueType.EXCEPTION, stack.getLiteralPath(), isInstanceOf(t, code), I18nConstants.SD_ED_TYPE_PROFILE_WRONG, p, t, code);
}
}
}
private boolean isInstanceOf(String t, String code) {
StructureDefinition sd = context.fetchTypeDefinition(t);
while (sd != null) {
if (sd.getType().equals(code)) {
return true;
}
sd = sd.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null;
if (sd != null && !sd.getAbstract()) {
sd = null;
}
}
return false;
}
private String determineBaseType(StructureDefinition sd) {
while (sd != null && !sd.hasType() && sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());

View File

@ -187,5 +187,13 @@ public class NodeStack {
return literalPath;
}
public int depth() {
if (parent == null) {
return 0;
} else {
return parent.depth()+1;
}
}
}

View File

@ -90,7 +90,7 @@ public class ValidationEngineTests {
}
if (!org.hl7.fhir.validation.tests.utilities.TestUtilities.silent)
System.out.println("Test102: Validate patient-example.xml in v1.0.2 version");
ValidationEngine ve = new ValidationEngine("hl7.fhir.r2.core#1.0.2", DEF_TX, null, FhirPublication.DSTU2, "41.0.2");
ValidationEngine ve = new ValidationEngine("hl7.fhir.r2.core#1.0.2", DEF_TX, null, FhirPublication.DSTU2, "1.0.2");
ve.setNoInvariantChecks(true);
OperationOutcome op = ve.validate(FhirFormat.XML, TestingUtilities.loadTestResourceStream("validator", "patient102.xml"), null);
if (!TestUtilities.silent)

View File

@ -19,7 +19,7 @@
<properties>
<hapi_fhir_version>5.1.0</hapi_fhir_version>
<validator_test_case_version>1.1.46</validator_test_case_version>
<validator_test_case_version>1.1.47-SNAPSHOT</validator_test_case_version>
<junit_jupiter_version>5.6.2</junit_jupiter_version>
<maven_surefire_version>3.0.0-M4</maven_surefire_version>
<jacoco_version>0.8.5</jacoco_version>