diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6b0a40d51..385fc07b7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1 +1,17 @@ +Validator: + +* Add support for $index on aggregators in FHIRPath +* don't fail with an exception if an unknown resource type appears in contained resource +* improved validation for some value sets that are based on unknown code systems +* add the -verbose parameter, and add additional verbose messages + +Conversion code: + * Ignoring abatementBoolean when converting from dstu2 to r4 + +Other code changes: + +* Fix rendering of slices so type on slicer is not hidden +* Fix rendering for most resources - remove empty tables (e.g. text element, that shouldn't render) +* Fix NPE rendering code systems with some kinds of properties +* Improve rendering of questionnaires (icons, option sets) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 6aefc6650..222732365 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -4044,6 +4044,9 @@ public class ProfileUtilities extends TranslatingUtilities { hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : "")); hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement())); } + if (element.hasSlicing()) { + sName = "Slices for "+sName; + } Cell left = gen.new Cell(null, ref, sName, hint, null); row.getCells().add(left); return left; @@ -4102,9 +4105,7 @@ public class ProfileUtilities extends TranslatingUtilities { } } else { res.add(genCardinality(gen, element, row, hasDef, used, null)); - if (element.hasSlicing()) - res.add(addCell(row, gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null))); - else if (hasDef && !"0".equals(element.getMax()) && typesRow == null) + if (hasDef && !"0".equals(element.getMax()) && typesRow == null) res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport)); else res.add(addCell(row, gen.new Cell())); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java index f635ac98d..e617c6f9b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/JsonParser.java @@ -366,11 +366,13 @@ public class JsonParser extends ParserBase { } else { String name = rt.getAsString(); StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs())); - if (sd == null) - throw new FHIRFormatError(context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name)); - parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities), SpecialElement.fromProperty(parent.getProperty()), elementProperty); - parent.setType(name); - parseChildren(npath, res, parent, true); + if (sd == null) { + logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL); + } else { + parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities), SpecialElement.fromProperty(parent.getProperty()), elementProperty); + parent.setType(name); + parseChildren(npath, res, parent, true); + } } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java index 253a39b65..92f1c17cb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java @@ -489,7 +489,7 @@ public class ExpressionNode { if (!name.startsWith("$")) return true; else - return Utilities.existsInList(name, "$this", "$total"); + return Utilities.existsInList(name, "$this", "$total", "$index"); } public Kind getKind() { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java index 4d283ca7f..4a40f4f6d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java @@ -701,18 +701,24 @@ public class ProfileDrivenRenderer extends ResourceRenderer { } } } else if (canDoTable(path, p, grandChildren, x)) { - x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); - XhtmlNode tbl = x.table("grid"); + XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader()); + xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName())))); + XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); + tbl.setAttribute("class", "grid"); XhtmlNode tr = tbl.tr(); tr.td().tx("-"); // work around problem with empty table rows - addColumnHeadings(tr, grandChildren); + boolean add = addColumnHeadings(tr, grandChildren); for (BaseWrapper v : p.getValues()) { if (v != null) { tr = tbl.tr(); tr.td().tx("*"); // work around problem with empty table rows - addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent); + add = addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent) || add; } } + if (add) { + x.add(xn); + x.add(tbl); + } } else if (isExtension(p)) { for (BaseWrapper v : p.getValues()) { if (v != null) { @@ -776,6 +782,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { if (x.getName().equals("p")) { return false; } + for (ElementDefinition e : grandChildren) { List values = getValues(path, p, e); if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e)) @@ -812,24 +819,32 @@ public class ProfileDrivenRenderer extends ResourceRenderer { return false; } - private void addColumnHeadings(XhtmlNode tr, List grandChildren) { - for (ElementDefinition e : grandChildren) + private boolean addColumnHeadings(XhtmlNode tr, List grandChildren) { + boolean b = false; + for (ElementDefinition e : grandChildren) { + b = true; tr.td().b().addText(Utilities.capitalize(tail(e.getPath()))); + } + return b; } - private void addColumnValues(ResourceWrapper res, XhtmlNode tr, List grandChildren, BaseWrapper v, boolean showCodeDetails, Map displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { + private boolean addColumnValues(ResourceWrapper res, XhtmlNode tr, List grandChildren, BaseWrapper v, boolean showCodeDetails, Map displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome { + boolean b = false; for (ElementDefinition e : grandChildren) { PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1)); XhtmlNode td = tr.td(); - if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) + if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) { + b = true; td.tx(" "); - else { + } else { for (BaseWrapper vv : p.getValues()) { + b = true; td.sep(", "); renderLeaf(res, vv, e, td, td, false, showCodeDetails, displayHints, path, indent); } } } + return b; } private void filterGrandChildren(List grandChildren, String string, PropertyWrapper prop) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index a4bb9aeeb..fc9efb397 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -14,6 +14,7 @@ import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Expression; import org.hl7.fhir.r5.model.Extension; +import org.hl7.fhir.r5.model.PrimitiveType; import org.hl7.fhir.r5.model.Questionnaire; import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; @@ -60,7 +61,11 @@ public class QuestionnaireRenderer extends TerminologyRenderer { public boolean renderTree(XhtmlNode x, Questionnaire q) throws UnsupportedEncodingException, IOException { boolean hasFlags = checkForFlags(q.getItem()); + boolean doOpts = context.getDefinitionsTarget() == null && hasAnyOptions(q.getItem()); + if (doOpts) { + x.b().tx("Structure"); + } HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); TableModel model = gen.new TableModel("qtree="+q.getId(), !forResource); model.setAlternating(true); @@ -83,9 +88,79 @@ public class QuestionnaireRenderer extends TerminologyRenderer { } XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); x.getChildNodes().add(xn); + if (doOpts) { + renderOptions(q, x); + } return hasExt; } + private void renderOptions(Questionnaire q, XhtmlNode x) { + if (hasAnyOptions(q.getItem())) { + x.hr(); + x.para().b().tx("Option Sets"); + renderOptions(q.getItem(), x); + } + } + + private void renderOptions(List items, XhtmlNode x) { + for (QuestionnaireItemComponent i : items) { + renderItemOptions(x, i); + renderOptions(i.getItem(), x); + } + } + + public void renderItemOptions(XhtmlNode x, QuestionnaireItemComponent i) { + if (i.hasAnswerOption()) { + boolean useSelect = false; + for (QuestionnaireItemAnswerOptionComponent opt : i.getAnswerOption()) { + useSelect = useSelect || opt.getInitialSelected(); + } + x.an("opt-item."+i.getLinkId()); + x.para().b().tx("Answer options for "+i.getLinkId()); + XhtmlNode ul = x.ul(); + for (QuestionnaireItemAnswerOptionComponent opt : i.getAnswerOption()) { + XhtmlNode li = ul.li(); + li.style("font-size: 11px"); + if (useSelect) { + if (opt.getInitialSelected()) { + li.img("icon-selected.png"); + } else { + li.img("icon-not-selected.png"); + } + } + if (opt.getValue().isPrimitive()) { + li.tx(opt.getValue().primitiveValue()); + } else if (opt.getValue() instanceof Coding) { + Coding c = (Coding) opt.getValue(); + String link = context.getWorker().getLinkForUrl(context.getSpecificationLink(), c.getSystem()); + if (link == null) { + li.tx(c.getSystem()+"#"+c.getCode()); + } else { + li.ah(link).tx(describeSystem(c.getSystem())); + li.tx(": "+c.getCode()); + } + if (c.hasDisplay()) { + li.tx(" (\""+c.getDisplay()+"\")"); + } + } else { + li.tx("??"); + } + } + } + } + + private boolean hasAnyOptions(List items) { + for (QuestionnaireItemComponent i : items) { + if (i.hasAnswerOption()) { + return true; + } + if (hasAnyOptions(i.getItem())) { + return true; + } + } + return false; + } + private boolean checkForFlags(List items) { for (QuestionnaireItemComponent i : items) { if (checkForFlags(i)) { @@ -228,7 +303,12 @@ public class QuestionnaireRenderer extends TerminologyRenderer { if (i.hasAnswerOption()) { if (!defn.getPieces().isEmpty()) defn.addPiece(gen.new Piece("br")); defn.getPieces().add(gen.new Piece(null, "Options: ", null)); - defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); + if (context.getDefinitionsTarget() == null) { + // if we don't have a definitions target, we'll add them below. + defn.getPieces().add(gen.new Piece("#opt-item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); + } else { + defn.getPieces().add(gen.new Piece(context.getDefinitionsTarget()+"#item."+i.getLinkId(), Integer.toString(i.getAnswerOption().size())+" "+Utilities.pluralize("option", i.getAnswerOption().size()), null)); + } } if (i.hasInitial()) { for (QuestionnaireItemInitialComponent v : i.getInitial()) { @@ -725,7 +805,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer { } } } else if (i.hasAnswerOption()) { - + renderItemOptions(select, i); } select.option("a", "??", false); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java index d90d652ba..b9fc4a258 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java @@ -256,7 +256,7 @@ public abstract class TerminologyRenderer extends ResourceRenderer { if (cs == null) { return null; } - ConceptDefinitionComponent cc = CodeSystemUtilities.getCode(cs, code); + ConceptDefinitionComponent cc = code == null ? null : CodeSystemUtilities.getCode(cs, code); return cc == null ? null : cc.getDisplay(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java index 5482a2645..03d7fbdf8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/CodeSystemUtilities.java @@ -430,6 +430,9 @@ public class CodeSystemUtilities { } public static ConceptDefinitionComponent getCode(CodeSystem cs, String code) { + if (code == null) { + return null; + } for (ConceptDefinitionComponent cc : cs.getConcept()) { ConceptDefinitionComponent cd = getCode(cc, code); if (cd != null) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java index 77369d3e9..4a768e362 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetCheckerSimple.java @@ -130,6 +130,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe ValidationResult res = null; boolean inExpansion = false; + boolean inInclude = false; String system = code.hasSystem() ? code.getSystem() : getValueSetSystemOrNull(); if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { if (system == null && !code.hasDisplay()) { // dealing with just a plain code (enum) @@ -142,6 +143,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe code.setSystem(system); } inExpansion = checkExpansion(code); + inInclude = checkInclude(code); CodeSystem cs = context.fetchCodeSystem(system); if (cs == null) { cs = findSpecialCodeSystem(system); @@ -181,26 +183,58 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe throw new FHIRException("No try the server"); } } else { - // disabled waiting for discussion - throw new FHIRException("No try the server"); -// inExpansion = checkExpansion(code); + inExpansion = checkExpansion(code); + inInclude = checkInclude(code); } // then, if we have a value set, we check it's in the value set if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { - if ((res==null || res.isOk()) && !codeInValueSet(system, code.getCode())) { - if (!inExpansion) { - res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR); - } else if (warningMessage!=null) { - res = new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage)); - } else { - res.setMessage("Code found in expansion, however: " + res.getMessage()); + if ((res==null || res.isOk())) { + Boolean ok = codeInValueSet(system, code.getCode()); + if (ok == null || !ok) { + if (res == null) { + res = new ValidationResult(null, null); + } + if (!inExpansion && !inInclude) { + res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR); + } else if (warningMessage!=null) { + res = new ValidationResult(IssueSeverity.WARNING, context.formatMessage(I18nConstants.CODE_FOUND_IN_EXPANSION_HOWEVER_, warningMessage)); + } else if (inExpansion) { + res.setMessage("Code found in expansion, however: " + res.getMessage()); + } else if (inInclude) { + res.setMessage("Code found in include, however: " + res.getMessage()); + } } } } return res; } + private boolean checkInclude(Coding code) { + if (valueset == null || code.getSystem() == null || code.getCode() == null) { + return false; + } + for (ConceptSetComponent inc : valueset.getCompose().getExclude()) { + if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { + for (ConceptReferenceComponent cc : inc.getConcept()) { + if (cc.hasCode() && cc.getCode().equals(code.getCode())) { + return false; + } + } + } + } + for (ConceptSetComponent inc : valueset.getCompose().getInclude()) { + if (inc.hasSystem() && inc.getSystem().equals(code.getSystem())) { + for (ConceptReferenceComponent cc : inc.getConcept()) { + if (cc.hasCode() && cc.getCode().equals(code.getCode())) { + return true; + } + } + } + } + return false; + } + private CodeSystem findSpecialCodeSystem(String system) { if ("urn:ietf:rfc:3986".equals(system)) { CodeSystem cs = new CodeSystem(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 23432ce6d..8fe761030 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -873,6 +873,7 @@ public class FHIRPathEngine { private Base thisItem; private List total; private Map aliases; + private int index; public ExecutionContext(Object appInfo, Base resource, Base rootResource, Base context, Map aliases, Base thisItem) { this.appInfo = appInfo; @@ -881,6 +882,7 @@ public class FHIRPathEngine { this.rootResource = rootResource; this.aliases = aliases; this.thisItem = thisItem; + this.index = 0; } public Base getFocusResource() { return focusResource; @@ -894,6 +896,14 @@ public class FHIRPathEngine { public List getTotal() { return total; } + + public void next() { + index++; + } + public Base getIndex() { + return new IntegerType(index); + } + public void addAlias(String name, List focus) throws FHIRException { if (aliases == null) { aliases = new HashMap(); @@ -908,6 +918,10 @@ public class FHIRPathEngine { public Base getAlias(String name) { return aliases == null ? null : aliases.get(name); } + public ExecutionContext setIndex(int i) { + index = i; + return this; + } } private class ExecutionTypeContext { @@ -1334,6 +1348,8 @@ public class FHIRPathEngine { work.add(context.getThisItem()); } else if (atEntry && exp.getName().equals("$total")) { work.addAll(context.getTotal()); + } else if (atEntry && exp.getName().equals("$index")) { + work.add(context.getIndex()); } else { for (Base item : focus) { List outcome = execute(context, item, exp, atEntry); @@ -1439,6 +1455,8 @@ public class FHIRPathEngine { result.update(context.getThisItem()); } else if (atEntry && exp.getName().equals("$total")) { result.update(anything(CollectionStatus.UNORDERED)); + } else if (atEntry && exp.getName().equals("$index")) { + result.addType(TypeDetails.FP_Integer); } else if (atEntry && focus == null) { result.update(executeContextType(context, exp.getName(), exp)); } else { @@ -4346,6 +4364,7 @@ public class FHIRPathEngine { for (Base item : focus) { ExecutionContext c = changeThis(context, item); c.total = total; + c.next(); total = execute(c, pc, exp.getParameters().get(0), true); } return total; @@ -5117,10 +5136,12 @@ public class FHIRPathEngine { private List funcSelect(ExecutionContext context, List focus, ExpressionNode exp) throws FHIRException { List result = new ArrayList(); List pc = new ArrayList(); + int i = 0; for (Base item : focus) { pc.clear(); pc.add(item); - result.addAll(execute(changeThis(context, item), pc, exp.getParameters().get(0), true)); + result.addAll(execute(changeThis(context, item).setIndex(i), pc, exp.getParameters().get(0), true)); + i++; } return result; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java index fc22c8938..0345bedd4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/OperationOutcomeUtilities.java @@ -65,6 +65,7 @@ public class OperationOutcomeUtilities { if (message.getSource() != null) { issue.getExtension().add(ToolingExtensions.makeIssueSource(message.getSource())); } + issue.setUserData("source.msg", message); return issue; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java index 49e2302d9..cbd5662d7 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/validation/ValidationMessage.java @@ -511,6 +511,7 @@ public class ValidationMessage implements Comparator, Compara private String locationLink; private String txLink; public String sliceHtml; + public String[] sliceText; private boolean slicingHint; private boolean signpost; @@ -771,8 +772,9 @@ public class ValidationMessage implements Comparator, Compara return sliceHtml; } - public void setSliceHtml(String sliceHtml) { + public void setSliceHtml(String sliceHtml, String[] text) { this.sliceHtml = sliceHtml; + this.sliceText = text; } public String getMessageId() { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java index 9d4ffdaec..da3947d4c 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/BaseValidator.java @@ -262,9 +262,9 @@ public class BaseValidator { * @return Returns thePass (in other words, returns true if the rule did not fail validation) */ //FIXME: formatMessage should be done here - protected boolean slicingHint(List errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html) { + protected boolean slicingHint(List errors, IssueType type, int line, int col, String path, boolean thePass, String msg, String html, String[] text) { if (!thePass) { - addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html); + addValidationMessage(errors, type, line, col, path, msg, IssueSeverity.INFORMATION, null).setSlicingHint(true).setSliceHtml(html, text); } return thePass; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java index d2285c768..66d34c653 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/services/ValidationService.java @@ -97,12 +97,12 @@ public class ValidationService { if (cliContext.getOutput() == null) { if (r instanceof Bundle) for (Bundle.BundleEntryComponent e : ((Bundle) r).getEntry()) - ec = ec + displayOperationOutcome((OperationOutcome) e.getResource(), ((Bundle) r).getEntry().size() > 1) + ec; + ec = ec + displayOperationOutcome((OperationOutcome) e.getResource(), ((Bundle) r).getEntry().size() > 1, validator.isCrumbTrails()) + ec; else if (r == null) { ec = ec + 1; System.out.println("No output from validation - nothing to validate"); } else { - ec = displayOperationOutcome((OperationOutcome) r, false); + ec = displayOperationOutcome((OperationOutcome) r, false, validator.isCrumbTrails()); } } else { IParser x; @@ -262,7 +262,7 @@ public class ValidationService { return sessionId; } - public int displayOperationOutcome(OperationOutcome oo, boolean hasMultiples) { + public int displayOperationOutcome(OperationOutcome oo, boolean hasMultiples, boolean crumbs) { int error = 0; int warn = 0; int info = 0; @@ -286,6 +286,16 @@ public class ValidationService { System.out.println((error == 0 ? "Success" : "*FAILURE*") + ": " + Integer.toString(error) + " errors, " + Integer.toString(warn) + " warnings, " + Integer.toString(info) + " notes"); for (OperationOutcome.OperationOutcomeIssueComponent issue : oo.getIssue()) { System.out.println(getIssueSummary(issue)); + if (crumbs) { + ValidationMessage vm = (ValidationMessage) issue.getUserData("source.msg"); + if (vm != null) { + if (vm.sliceText != null) { + for (String s : vm.sliceText) { + System.out.println(" slice info: "+s); + } + } + } + } } if (hasMultiples) { System.out.print("---"); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java index 6b90b3160..650803ce1 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/Params.java @@ -56,6 +56,7 @@ public class Params { public static final String WANT_INVARIANTS_IN_MESSAGES = "-want-invariants-in-messages"; public static final String SECURITY_CHECKS = "-security-checks"; public static final String CRUMB_TRAIL = "-crumb-trails"; + public static final String VERBOSE = "-verbose"; public static final String SHOW_TIMES = "-show-times"; public static final String ALLOW_EXAMPLE_URLS = "-allow-example-urls"; @@ -183,6 +184,8 @@ public class Params { cliContext.setSecurityChecks(true); } else if (args[i].equals(CRUMB_TRAIL)) { cliContext.setCrumbTrails(true); + } else if (args[i].equals(VERBOSE)) { + cliContext.setCrumbTrails(true); } else if (args[i].equals(ALLOW_EXAMPLE_URLS)) { String bl = args[++i]; if ("true".equals(bl)) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 2b21cff2c..bded58268 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -2722,7 +2722,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, areAllBaseProfiles(profiles), I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); for (StructureDefinition sd : badProfiles.keySet()) { slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, - context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), errorSummaryForSlicingAsHtml(badProfiles.get(sd))); + context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), + errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); } } else { rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path, profiles.size() == 1, I18nConstants.REFERENCE_REF_CANTMATCHCHOICE, ref, asList(type.getTargetProfile())); @@ -2738,7 +2739,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!isShowMessagesFromReferences()) { warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); for (StructureDefinition sd : badProfiles.keySet()) { - slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), errorSummaryForSlicingAsHtml(badProfiles.get(sd))); + slicingHint(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, ref, sd.getUrl()), + errorSummaryForSlicingAsHtml(badProfiles.get(sd)), errorSummaryForSlicingAsText(badProfiles.get(sd))); } } else { warning(errors, IssueType.STRUCTURE, element.line(), element.col(), path, false, I18nConstants.REFERENCE_REF_MULTIPLEMATCHES, ref, asListByUrl(goodProfiles.keySet())); @@ -2864,6 +2866,24 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return "
    " + b.toString() + "
"; } + private String[] errorSummaryForSlicingAsText(List list) { + List res = new ArrayList(); + for (ValidationMessage vm : list) { + if (vm.isSlicingHint()) { + if (vm.sliceText != null) { + for (String s : vm.sliceText) { + res.add(vm.getLocation() + ": " + s); + } + } else { + res.add(vm.getLocation() + ": " + vm.getMessage()); + } + } else if (vm.getLevel() == IssueSeverity.ERROR || vm.getLevel() == IssueSeverity.FATAL) { + res.add(vm.getLocation() + ": " + vm.getHtml()); + } + } + return res.toArray(new String[0]); + } + private TypeRefComponent getReferenceTypeRef(List types) { for (TypeRefComponent tr : types) { if ("Reference".equals(tr.getCode())) { @@ -3619,13 +3639,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat ValidatorHostContext shc = hostContext.forSlicing(); boolean pass = evaluateSlicingExpression(shc, element, path, profile, n); if (!pass) { - slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString())); + slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, (context.formatMessage(I18nConstants.DOES_NOT_MATCH_SLICE_, ed.getSliceName())), "discriminator = " + Utilities.escapeXml(n.toString()), null); for (String url : shc.getSliceRecords().keySet()) { slicingHint(sliceInfo, IssueType.STRUCTURE, element.line(), element.col(), path, false, context.formatMessage(I18nConstants.DETAILS_FOR__MATCHING_AGAINST_PROFILE_, stack.getLiteralPath(), url), context.formatMessage(I18nConstants.PROFILE__DOES_NOT_MATCH_FOR__BECAUSE_OF_THE_FOLLOWING_PROFILE_ISSUES__, url, - stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url)))); + stack.getLiteralPath(), errorSummaryForSlicingAsHtml(shc.getSliceRecords().get(url))), errorSummaryForSlicingAsText(shc.getSliceRecords().get(url))); } } return pass; @@ -4830,7 +4850,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat slicingHint(errors, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : " defined in the profile " + profile.getUrl()), - context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo)); + context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : I18nConstants.DEFINED_IN_THE_PROFILE + profile.getUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), + errorSummaryForSlicingAsText(ei.sliceInfo)); } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { rule(errors, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getUrl()), errorSummaryForSlicing(ei.sliceInfo)); } @@ -5337,7 +5358,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat public ValidationResult checkCodeOnServer(NodeStack stack, ValueSet valueset, Coding c, boolean checkMembership) { if (checkMembership) { - return context.validateCode(new ValidationOptions(stack.getWorkingLang()), c, valueset); + return context.validateCode(new ValidationOptions(stack.getWorkingLang()).checkValueSetOnly(), c, valueset); } else { return context.validateCode(new ValidationOptions(stack.getWorkingLang()).noCheckValueSetMembership(), c, valueset); } diff --git a/pom.xml b/pom.xml index a9e72df6c..d64c81ba9 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 5.1.0 - 1.1.64 + 1.1.65-SNAPSHOT 5.7.1 1.7.1 3.0.0-M4