diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index 7074a6ca6..6ee86a526 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java index 2ed89a96f..75a2bc1c9 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/VSACImporter.java @@ -1,5 +1,6 @@ package org.hl7.fhir.convertors.misc; +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; @@ -28,10 +29,10 @@ public class VSACImporter extends OIDBasedValueSetImporter { public static void main(String[] args) throws FHIRException, IOException, ParseException, URISyntaxException { VSACImporter self = new VSACImporter(); - self.process(args[0], args[1], args[2]); + self.process(args[0], args[1], args[2], "true".equals(args[3])); } - private void process(String source, String dest, String apiKey) throws FHIRException, IOException, URISyntaxException { + private void process(String source, String dest, String apiKey, boolean onlyNew) throws FHIRException, IOException, URISyntaxException { CSVReader csv = new CSVReader(new FileInputStream(source)); csv.readHeaders(); Map errs = new HashMap<>(); @@ -39,23 +40,25 @@ public class VSACImporter extends OIDBasedValueSetImporter { FHIRToolingClient fhirToolingClient = new FHIRToolingClient("https://cts.nlm.nih.gov/fhir", "fhir/vsac"); fhirToolingClient.setUsername("apikey"); fhirToolingClient.setPassword(apiKey); - fhirToolingClient.setTimeout(30000); + fhirToolingClient.setTimeout(120000); int i = 0; int j = 0; while (csv.line()) { String oid = csv.cell("OID"); try { - ValueSet vs = fhirToolingClient.read(ValueSet.class, oid); - try { - ValueSet vse = fhirToolingClient.expandValueset(vs.getUrl(), null); - vs.setExpansion(vse.getExpansion()); - j++; - } catch (Exception e) { - errs.put(oid, "Expansion: " +e.getMessage()); - System.out.println(e.getMessage()); + if (!onlyNew || !(new File(Utilities.path(dest, "ValueSet-" + oid + ".json")).exists())) { + ValueSet vs = fhirToolingClient.read(ValueSet.class, oid); + try { + ValueSet vse = fhirToolingClient.expandValueset(vs.getUrl(), null); + vs.setExpansion(vse.getExpansion()); + j++; + } catch (Exception e) { + errs.put(oid, "Expansion: " +e.getMessage()); + System.out.println(e.getMessage()); + } + new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + oid + ".json")), vs); } - new JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(dest, "ValueSet-" + oid + ".json")), vs); i++; if (i % 100 == 0) { System.out.println(":"+i+" ("+j+")"); diff --git a/org.hl7.fhir.core.generator/configuration/CodeableConcept.java b/org.hl7.fhir.core.generator/configuration/CodeableConcept.java index c1ff36027..ccf28700d 100644 --- a/org.hl7.fhir.core.generator/configuration/CodeableConcept.java +++ b/org.hl7.fhir.core.generator/configuration/CodeableConcept.java @@ -93,4 +93,9 @@ public boolean hasCoding(String system, String code) { public void addCoding(String system, String code, String display) { getCoding().add(new Coding(system, code, display)); } - \ No newline at end of file + + @Override + public String toString() { + return hasCoding() ? getCoding().toString() : "["+getText()+"]"; + } + \ No newline at end of file diff --git a/org.hl7.fhir.core.generator/configuration/OperationOutcomeIssueComponent.java b/org.hl7.fhir.core.generator/configuration/OperationOutcomeIssueComponent.java new file mode 100644 index 000000000..85753da3b --- /dev/null +++ b/org.hl7.fhir.core.generator/configuration/OperationOutcomeIssueComponent.java @@ -0,0 +1,8 @@ +@Override +public String toString() { + if (getExpression().size() == 1) { + return getExpression().get(0)+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText(); + } else { + return getExpression()+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText(); + } +} \ No newline at end of file diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 14321a834..40f772cc5 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index 150a1cbb9..226adca1b 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 02fc1b43d..ce47ad9ef 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index bded6d599..5f60a0ce4 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml index ab4b37309..3bf6cd357 100644 --- a/org.hl7.fhir.r4b/pom.xml +++ b/org.hl7.fhir.r4b/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/renderers/DataRenderer.java b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/renderers/DataRenderer.java index 3d6a46ac9..9272bc041 100644 --- a/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/renderers/DataRenderer.java +++ b/org.hl7.fhir.r4b/src/main/java/org/hl7/fhir/r4b/renderers/DataRenderer.java @@ -342,7 +342,7 @@ public class DataRenderer extends Renderer { return true; } if (Utilities.existsInList(t, - "ActivityDefinition", "CapabilityStatement", "CapabilityStatement2", "ChargeItemDefinition", "Citation", "CodeSystem", + "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" )) diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 82ed79df6..030a3194a 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java index 65ac7a124..dcbe9e08b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/AdditionalBindingsRenderer.java @@ -31,6 +31,7 @@ public class AdditionalBindingsRenderer { private String purpose; private String valueSet; private String doco; + private String docoShort; private UsageContext usage; private boolean any = false; private boolean isUnchanged = false; @@ -52,6 +53,9 @@ public class AdditionalBindingsRenderer { private boolean alreadyMatched() { return matched; } + public String getDoco(boolean full) { + return full ? doco : docoShort; + } public boolean unchanged() { if (!isUnchanged) return false; @@ -161,6 +165,7 @@ public class AdditionalBindingsRenderer { abr.purpose = ext.getExtensionString("purpose"); abr.valueSet = ext.getExtensionString("valueSet"); abr.doco = ext.getExtensionString("documentation"); + abr.docoShort = ext.getExtensionString("shortDoco"); abr.usage = (ext.hasExtension("usage")) && ext.getExtensionByUrl("usage").hasValueUsageContext() ? ext.getExtensionByUrl("usage").getValueUsageContext() : null; abr.any = "any".equals(ext.getExtensionString("scope")); abr.isUnchanged = ext.hasUserData(ProfileUtilities.DERIVATION_EQUALS); @@ -188,12 +193,12 @@ public class AdditionalBindingsRenderer { } } - private void render(List children, boolean doDoco) throws FHIRFormatError, DefinitionException, IOException { + public void render(List children, boolean fullDoco) throws FHIRFormatError, DefinitionException, IOException { boolean doco = false; boolean usage = false; boolean any = false; for (AdditionalBindingDetail binding : bindings) { - doco = doco || (doDoco && (binding.doco != null || (binding.compare!=null && binding.compare.doco!=null))); + doco = doco || binding.getDoco(fullDoco)!=null || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null); usage = usage || binding.usage != null || (binding.compare!=null && binding.compare.usage!=null); any = any || binding.any || (binding.compare!=null && binding.compare.any); } @@ -266,8 +271,8 @@ public class AdditionalBindingsRenderer { } if (doco) { if (binding.doco != null) { - String d = md.processMarkdown("Binding.description", binding.doco); - String oldD = binding.compare==null ? null : md.processMarkdown("Binding.description.compare", binding.compare.doco); + String d = md.processMarkdown("Binding.description", fullDoco ? binding.doco : binding.docoShort); + String oldD = binding.compare==null ? null : md.processMarkdown("Binding.description.compare", fullDoco ? binding.compare.doco : binding.compare.docoShort); tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD)); } else { tr.td().style("font-size: 11px"); @@ -307,7 +312,10 @@ public class AdditionalBindingsRenderer { td.ah(corePath+"extension-elementdefinition-minvalueset.html", "The minimum allowable value set - any conformant system SHALL support all these codes").tx("Min Binding"); break; case "conformance" : - td.ah(corePath+"terminologies.html#strength", "Validators will check this binding (strength = required)").tx("Validation Criteria"); + td.ah(corePath+"terminologies.html#strength", "Validators will check this binding (strength = required)").tx("Validation Binding"); + break; + case "candidate" : + td.ah(corePath+"terminologies.html#strength", "This is a candidate binding that constraints on this profile may consider (see doco)").tx("Candidate Validation Binding"); break; case "current" : td.span(null, "New records are required to use this value set, but legacy records may use other codes").tx("Required"); @@ -336,5 +344,9 @@ public class AdditionalBindingsRenderer { return br; } + public boolean hasBindings() { + return !bindings.isEmpty(); + } + } 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 dd41ac3f0..b79e31157 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 @@ -4998,6 +4998,8 @@ public class ProfileUtilities extends TranslatingUtilities { b.setDescription(o.getDescription()); b.getDescriptionElement().setUserData(DERIVATION_EQUALS, o.getDescriptionElement()); } + // todo: derivation? + b.getExtension().addAll(binding.getExtension()); return b; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java index 7bad1a90f..e6294e210 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/ParserBase.java @@ -43,6 +43,7 @@ import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.utilities.Utilities; @@ -92,10 +93,13 @@ public abstract class ParserBase { public enum ValidationPolicy { NONE, QUICK, EVERYTHING } public boolean isPrimitive(String code) { - return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml", "url", "canonical"); + StructureDefinition sd = context.fetchTypeDefinition(code); + if (sd != null) { + return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; + } + + return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "uuid", "xhtml", "url", "canonical"); -// StructureDefinition sd = context.fetchTypeDefinition(code); -// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; } protected IWorkerContext context; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index dde9e9c04..0c5a0ce9c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -155,7 +155,13 @@ public class XmlParser extends ParserBase { doc = builder.parse(stream); } } catch (Exception e) { - logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); + if (e.getMessage().contains("lineNumber:") && e.getMessage().contains("columnNumber:")) { + int line = Utilities.parseInt(extractVal(e.getMessage(), "lineNumber"), 0); + int col = Utilities.parseInt(extractVal(e.getMessage(), "columnNumber"), 0); + logError(line, col, "(xml)", IssueType.INVALID, e.getMessage().substring(e.getMessage().lastIndexOf(";")+1).trim(), IssueSeverity.FATAL); + } else { + logError(0, 0, "(xml)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); + } doc = null; } if (doc != null) { @@ -168,12 +174,17 @@ public class XmlParser extends ParserBase { } + private String extractVal(String src, String name) { + src = src.substring(src.indexOf(name)+name.length()+1); + src = src.substring(0, src.indexOf(";")).trim(); + return src; + } private void checkForProcessingInstruction(Document document) throws FHIRFormatError { if (policy == ValidationPolicy.EVERYTHING && FormatUtilities.FHIR_NS.equals(document.getDocumentElement().getNamespaceURI())) { Node node = document.getFirstChild(); while (node != null) { if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) - logError(line(document), col(document), "(document)", IssueType.INVALID, context.formatMessage( + logError(line(document, false), col(document, false), "(document)", IssueType.INVALID, context.formatMessage( I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR); node = node.getNextSibling(); } @@ -181,14 +192,14 @@ public class XmlParser extends ParserBase { } - private int line(Node node) { + private int line(Node node, boolean end) { XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); - return loc == null ? 0 : loc.getStartLine(); + return loc == null ? 0 : end ? loc.getEndLine() : loc.getStartLine(); } - private int col(Node node) { + private int col(Node node, boolean end) { XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); - return loc == null ? 0 : loc.getStartColumn(); + return loc == null ? 0 : end ? loc.getEndColumn() : loc.getStartColumn(); } public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException { @@ -202,14 +213,14 @@ public class XmlParser extends ParserBase { String name = element.getLocalName(); String path = "/"+pathPrefix(ns)+name; - StructureDefinition sd = getDefinition(line(element), col(element), (ns == null ? "noNamespace" : ns), name); + StructureDefinition sd = getDefinition(line(element, false), col(element, false), (ns == null ? "noNamespace" : ns), name); if (sd == null) return null; Element result = new Element(element.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)); result.setPath(element.getLocalName()); checkElement(element, path, result.getProperty()); - result.markLocation(line(element), col(element)); + result.markLocation(line(element, false), col(element, false)); result.setType(element.getLocalName()); parseChildren(path, element, result); result.numberChildren(); @@ -253,14 +264,14 @@ public class XmlParser extends ParserBase { private void checkElement(org.w3c.dom.Element element, String path, Property prop) throws FHIRFormatError { if (policy == ValidationPolicy.EVERYTHING) { if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR Content - logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); + logError(line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR); String ns = prop.getXmlNamespace(); String elementNs = element.getNamespaceURI(); if (elementNs == null) { elementNs = "noNamespace"; } if (!elementNs.equals(ns)) - logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR); + logError(line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR); } } @@ -282,8 +293,8 @@ public class XmlParser extends ParserBase { List properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node)); String text = XMLUtil.getDirectText(node).trim(); - int line = line(node); - int col = col(node); + int line = line(node, false); + int col = col(node, false); if (!Utilities.noString(text)) { Property property = getTextProp(properties); if (property != null) { @@ -307,16 +318,19 @@ public class XmlParser extends ParserBase { Node n = node.getFirstChild(); while (n != null) { if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) { - while (n.getNextSibling() != null && n.getNodeType() != Node.ELEMENT_NODE) { - n = n.getNextSibling(); - } - Node nt = n; + Node nt = n; // try to find the nearest element for a line/col location + boolean end = false; while (nt.getPreviousSibling() != null && nt.getNodeType() != Node.ELEMENT_NODE) { nt = nt.getPreviousSibling(); + end = true; } - line = line(nt); - col = col(nt); - logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.TEXT_SHOULD_NOT_BE_PRESENT, Utilities.makeSingleLine(text)), IssueSeverity.ERROR); + while (nt.getNextSibling() != null && nt.getNodeType() != Node.ELEMENT_NODE) { + nt = nt.getNextSibling(); + end = false; + } + line = line(nt, end); + col = col(nt, end); + logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.TEXT_SHOULD_NOT_BE_PRESENT, Utilities.makeSingleLine(n.getTextContent().trim())), IssueSeverity.ERROR); } n = n.getNextSibling(); } @@ -351,8 +365,9 @@ public class XmlParser extends ParserBase { } else ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR content ok = ok || (hasTypeAttr(element) && attr.getLocalName().equals("type") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so - if (!ok) - logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR); + if (!ok) { + logError(line(node, false), col(node, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR); + } } } } @@ -376,12 +391,12 @@ public class XmlParser extends ParserBase { xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child); else xhtml = new XhtmlParser().setValidatorMode(true).parseHtmlNode((org.w3c.dom.Element) child); - Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child), col(child)); + Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false)); n.setPath(element.getPath()+"."+property.getName()); element.getChildren().add(n); } else { String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName(); - Element n = new Element(child.getLocalName(), property).markLocation(line(child), col(child)); + Element n = new Element(child.getLocalName(), property).markLocation(line(child, false), col(child, false)); if (property.isList()) { n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]"); } else { @@ -397,7 +412,7 @@ public class XmlParser extends ParserBase { xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); n.setType(xsiType); } else { - logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR); + logError(line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR); ok = false; } } else { @@ -418,11 +433,11 @@ public class XmlParser extends ParserBase { } } } else - logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR); + logError(line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR); } else if (child.getNodeType() == Node.CDATA_SECTION_NODE){ - logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR); + logError(line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR); } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) { - logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR); + logError(line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR); } child = child.getNextSibling(); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java index 1705d65a3..a75dc1ed0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/CodeableConcept.java @@ -404,6 +404,11 @@ public boolean hasCoding(String system, String code) { public void addCoding(String system, String code, String display) { getCoding().add(new Coding(system, code, display)); } + + @Override + public String toString() { + return hasCoding() ? getCoding().toString() : "["+getText()+"]"; + } // end addition diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/OperationOutcome.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/OperationOutcome.java index 603aba49e..f34dfed78 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/OperationOutcome.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/OperationOutcome.java @@ -1299,6 +1299,15 @@ For resource issues, this will be a simple XPath limited to element names, repet } + @Override + public String toString() { + if (getExpression().size() == 1) { + return getExpression().get(0)+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText(); + } else { + return getExpression()+" "+getDiagnostics()+" "+getSeverity().toCode()+"/"+getCode().toCode()+": "+getDetails().getText(); + } + } + } /** diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java index 1a28531b8..4177795e2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java @@ -79,8 +79,13 @@ public class BundleRenderer extends ResourceRenderer { if (be.has("fullUrl")) { root.an(makeInternalBundleLink(be.get("fullUrl").primitiveValue())); } - if (be.has("resource") && be.getChildByName("resource").getValues().get(0).has("id")) { - root.an(be.get("resource").fhirType() + "_" + be.getChildByName("resource").getValues().get(0).get("id").primitiveValue()); + if (be.has("resource")) { + if (be.getChildByName("resource").getValues().get(0).has("id")) { + root.an(be.get("resource").fhirType() + "_" + be.getChildByName("resource").getValues().get(0).get("id").primitiveValue()); + } else { + String id = makeIdFromBundleEntry(be.get("fullUrl").primitiveValue()); + root.an(be.get("resource").fhirType() + "_" + id); + } } root.hr(); if (be.has("fullUrl")) { @@ -101,6 +106,7 @@ public class BundleRenderer extends ResourceRenderer { if (xn == null || xn.isEmpty()) { ResourceRenderer rr = RendererFactory.factory(rw, context); try { + rr.setRcontext(new ResourceContext(rcontext, rw)); xn = rr.render(rw); } catch (Exception e) { xn = new XhtmlNode(); @@ -128,7 +134,7 @@ public class BundleRenderer extends ResourceRenderer { if (subject.hasNarrative()) { x.addChildren(subject.getNarrative()); } else { - RendererFactory.factory(subject, context).render(x, subject); + RendererFactory.factory(subject, context, new ResourceContext(rcontext, subject)).render(x, subject); } } x.hr(); @@ -204,12 +210,14 @@ public class BundleRenderer extends ResourceRenderer { if (nx != null && !nx.isEmpty()) { x.addChildren(nx); } else { - RendererFactory.factory(subject, context).render(x, subject); + RendererFactory.factory(subject, context).setRcontext(new ResourceContext(rcontext, subject)).render(x, subject); } } x.hr(); if (!comp.getText().hasDiv()) { - ResourceRenderer rr = RendererFactory.factory(comp, getContext()); rr.render(comp); + ResourceRenderer rr = RendererFactory.factory(comp, getContext()); + rr.setRcontext(new ResourceContext(rcontext, comp)); + rr.render(comp); } if (comp.getText().hasDiv()) { x.addChildren(comp.getText().getDiv()); @@ -292,8 +300,14 @@ public class BundleRenderer extends ResourceRenderer { if (i > start) { if (be.hasFullUrl()) x.an(makeInternalBundleLink(be.getFullUrl())); - if (be.hasResource() && be.getResource().hasId()) - x.an(be.getResource().getResourceType().name() + "_" + be.getResource().getId()); + if (be.hasResource()) { + if (be.getResource().hasId()) { + x.an(be.getResource().getResourceType().name() + "_" + be.getResource().getId()); + } else { + String id = makeIdFromBundleEntry(be.getFullUrl()); + x.an(be.getResource().getResourceType().name() + "_" + id); + } + } x.hr(); if (docMode) { if (be.hasFullUrl() && be.hasResource()) { @@ -329,6 +343,7 @@ public class BundleRenderer extends ResourceRenderer { if (xn == null || xn.isEmpty()) { ResourceRenderer rr = RendererFactory.factory(be.getResource(), context); try { + rr.setRcontext(new ResourceContext(rcontext, be.getResource())); xn = rr.build(be.getResource()); } catch (Exception e) { xn = makeExceptionXhtml(e, "generating narrative"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index d807089b6..e1d39beec 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -341,7 +341,7 @@ public class DataRenderer extends Renderer { return true; } if (Utilities.existsInList(t, - "ActivityDefinition", "CapabilityStatement", "CapabilityStatement2", "ChargeItemDefinition", "Citation", "CodeSystem", + "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" )) 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 ba060dfe6..e876d539b 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 @@ -381,7 +381,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { renderContactPoint(x, c); } } else if (e instanceof UriType) { - renderUri(x, (UriType) e, defn.getPath(), rcontext != null && rcontext.getResourceResource() != null ? rcontext.getResourceResource().getId() : null); + renderUri(x, (UriType) e, defn.getPath(), rcontext != null && rcontext.getResource() != null ? rcontext.getResource().getId() : null); } else if (e instanceof Timing) { renderTiming(x, (Timing) e); } else if (e instanceof Range) { @@ -418,6 +418,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer { ctxtc.setAddGeneratedNarrativeHeader(false); ctxtc.setContained(true); ResourceRenderer rr = RendererFactory.factory(rw, ctxtc); + rr.setRcontext(new ResourceContext(rcontext, rw)); rr.render(parent.blockquote(), rw); } else { x.ah(ref).tx("See "+rw.fhirType()); @@ -690,12 +691,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer { x.para().b().tx("Generated Narrative: "+profile.present()+(showCodeDetails ? " with Details" : "")); } try { - generateByProfile(rcontext.getResourceResource(), profile, rcontext.getResourceResource(), profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile, profile.getSnapshot().getElement(), rcontext.getResourceResource().getResourceType().toString()), x, rcontext.getResourceResource().getResourceType().toString(), showCodeDetails); + generateByProfile(rcontext.getResource(), profile, rcontext.getResource(), profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile, profile.getSnapshot().getElement(), rcontext.getResource().getResourceType().toString()), x, rcontext.getResource().getResourceType().toString(), showCodeDetails); } catch (Exception e) { e.printStackTrace(); x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); } - inject(rcontext.getResourceResource(), x, NarrativeStatus.GENERATED); + inject((DomainResource) rcontext.getResource(), x, NarrativeStatus.GENERATED); return true; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index 9ba121ce7..df0b24eb4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -61,6 +61,15 @@ public abstract class ResourceRenderer extends DataRenderer { this.rcontext = rcontext; } + public ResourceContext getRcontext() { + return rcontext; + } + + public ResourceRenderer setRcontext(ResourceContext rcontext) { + this.rcontext = rcontext; + return this; + } + public XhtmlNode build(Resource dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); render(x, dr); @@ -324,7 +333,11 @@ public abstract class ResourceRenderer extends DataRenderer { if (rcontext != null) { BundleEntryComponent bundleResource = rcontext.resolve(url); if (bundleResource != null) { - String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + bundleResource.getResource().getId(); + String id = bundleResource.getResource().getId(); + if (id == null) { + id = makeIdFromBundleEntry(bundleResource.getFullUrl()); + } + String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + id; return new ResourceWithReference(bundleUrl, new ResourceWrapperDirect(this.context, bundleResource.getResource())); } org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url, version); @@ -350,6 +363,16 @@ public abstract class ResourceRenderer extends DataRenderer { } + protected String makeIdFromBundleEntry(String url) { + if (url == null) { + return null; + } + if (url.startsWith("urn:uuid:")) { + return url.substring(9).toLowerCase(); + } + return fullUrlToAnchor(url); + } + private String fullUrlToAnchor(String url) { return url.replace(":", "").replace("/", "_"); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index 8b036031f..88e3819ee 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -299,6 +299,10 @@ public class DirectWrappers { return new PropertyWrapperDirect(context, p); } + public Resource getResource() { + return wrapped; + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index 45a835ab8..5765a627b 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -286,6 +286,10 @@ public class ElementWrappers { return null; } + public Element getElement() { + return wrapped; + } + } public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java index 40b0b12c2..724466b3f 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/Resolver.java @@ -7,15 +7,10 @@ import org.hl7.fhir.r5.model.Parameters; import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; -import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContextType; import org.w3c.dom.Element; public class Resolver { - public enum ResourceContextType { - PARAMETERS, BUNDLE - } - public interface IReferenceResolver { ResourceWithReference resolve(RenderingContext context, String url); @@ -24,27 +19,24 @@ public class Resolver { } public static class ResourceContext { - private ResourceContextType type; - private Resource containerResource; - private org.hl7.fhir.r5.elementmodel.Element containerElement; + private ResourceContext container; - DomainResource resourceResource; - org.hl7.fhir.r5.elementmodel.Element resourceElement; + Resource resource; + org.hl7.fhir.r5.elementmodel.Element element; - public ResourceContext(ResourceContextType type, Resource bundle, DomainResource dr) { + public ResourceContext(ResourceContext container, Resource dr) { super(); - this.type = type; - this.containerResource = bundle; - this.resourceResource = dr; + this.container = container; + this.resource = dr; } - public ResourceContext(ResourceContextType type, org.hl7.fhir.r5.elementmodel.Element bundle, org.hl7.fhir.r5.elementmodel.Element dr) { + public ResourceContext(ResourceContext container, org.hl7.fhir.r5.elementmodel.Element dr) { super(); - this.type = type; - this.containerElement = bundle; - this.resourceElement = dr; + this.container = container; + this.element = dr; } + // public ResourceContext(Object bundle, Element doc) { // // TODO Auto-generated constructor stub // } @@ -54,22 +46,45 @@ public class Resolver { // } + public ResourceContext(ResourceContext container, ResourceWrapper rw) { + super(); + this.container = container; + // todo: howto do this better? + + if (rw instanceof DirectWrappers.ResourceWrapperDirect) { + this.resource = ((DirectWrappers.ResourceWrapperDirect) rw).getResource(); + } else if (rw instanceof ElementWrappers.ResourceWrapperMetaElement) { + this.element = ((ElementWrappers.ResourceWrapperMetaElement) rw).getElement(); + } else { + throw new Error("Not supported yet"); + } + } + + public ResourceContext getContainer() { + return container; + } + + public void setContainer(ResourceContext container) { + this.container = container; + } + // public org.hl7.fhir.r5.elementmodel.Element getBundleElement() { // return containerElement; // } // - public DomainResource getResourceResource() { - return resourceResource; + public Resource getResource() { + return resource; } - public org.hl7.fhir.r5.elementmodel.Element getResourceElement() { - return resourceElement; + public org.hl7.fhir.r5.elementmodel.Element getElement() { + return element; } public BundleEntryComponent resolve(String value) { if (value.startsWith("#")) { - if (resourceResource != null) { - for (Resource r : resourceResource.getContained()) { + if (resource instanceof DomainResource) { + DomainResource dr = (DomainResource) resource; + for (Resource r : dr.getContained()) { if (r.getId().equals(value.substring(1))) { BundleEntryComponent be = new BundleEntryComponent(); be.setResource(r); @@ -79,44 +94,45 @@ public class Resolver { } return null; } - if (type == ResourceContextType.BUNDLE) { - if (containerResource != null) { - for (BundleEntryComponent be : ((Bundle) containerResource).getEntry()) { - if (be.getFullUrl().equals(value)) - return be; - if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) - return be; + + if (resource instanceof Bundle) { + Bundle b = (Bundle) resource; + for (BundleEntryComponent be : b.getEntry()) { + if (be.getFullUrl().equals(value)) + return be; + if (value.equals(be.getResource().fhirType()+"/"+be.getResource().getId())) + return be; + } + } + + if (resource instanceof Parameters) { + Parameters pp = (Parameters) resource; + for (ParametersParameterComponent p : pp.getParameter()) { + if (p.getResource() != null && value.equals(p.getResource().fhirType()+"/"+p.getResource().getId())) { + BundleEntryComponent be = new BundleEntryComponent(); + be.setResource(p.getResource()); + return be; + } - } - } - if (type == ResourceContextType.PARAMETERS) { - if (containerResource != null) { - for (ParametersParameterComponent p : ((Parameters) containerResource).getParameter()) { - if (p.getResource() != null && value.equals(p.getResource().fhirType()+"/"+p.getResource().getId())) { - BundleEntryComponent be = new BundleEntryComponent(); - be.setResource(p.getResource()); - return be; - - } - } - } - } - return null; + } + } + + return container != null ? container.resolve(value) : null; } public org.hl7.fhir.r5.elementmodel.Element resolveElement(String value, String version) { if (value.startsWith("#")) { - if (resourceElement != null) { - for (org.hl7.fhir.r5.elementmodel.Element r : resourceElement.getChildrenByName("contained")) { + if (element != null) { + for (org.hl7.fhir.r5.elementmodel.Element r : element.getChildrenByName("contained")) { if (r.getChildValue("id").equals(value.substring(1))) return r; } } return null; } - if (type == ResourceContextType.BUNDLE) { - if (containerElement != null) { - for (org.hl7.fhir.r5.elementmodel.Element be : containerElement.getChildren("entry")) { + if (element != null) { + if (element.fhirType().equals("Bundle")) { + for (org.hl7.fhir.r5.elementmodel.Element be : element.getChildren("entry")) { org.hl7.fhir.r5.elementmodel.Element res = be.getNamedChild("resource"); if (res != null) { if (value.equals(be.getChildValue("fullUrl"))) { @@ -132,10 +148,8 @@ public class Resolver { } } } - } - if (type == ResourceContextType.PARAMETERS) { - if (containerElement != null) { - for (org.hl7.fhir.r5.elementmodel.Element p : containerElement.getChildren("parameter")) { + if (element.fhirType().equals("Parameters")) { + for (org.hl7.fhir.r5.elementmodel.Element p : element.getChildren("parameter")) { org.hl7.fhir.r5.elementmodel.Element res = p.getNamedChild("resource"); if (res != null && value.equals(res.fhirType()+"/"+res.getChildValue("id"))) { if (checkVersion(version, res)) { @@ -145,7 +159,7 @@ public class Resolver { } } } - return null; + return container != null ? container.resolveElement(value, version) : null; } private boolean checkVersion(String version, org.hl7.fhir.r5.elementmodel.Element res) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java index 4735bf332..71d22a073 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/JurisdictionUtilities.java @@ -11,7 +11,7 @@ public class JurisdictionUtilities { } public static String getJurisdictionFromLocale(String s) { - if (Utilities.existsInList(s, + if (Utilities.existsInList(s.toUpperCase(), "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", "BJ", "BL", "BM", @@ -41,7 +41,7 @@ public class JurisdictionUtilities { "UZ", "VA", "VC", "VE", "VG", "VI", "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW" )) { - return "urn:iso:std:iso:3166#"+s; + return "urn:iso:std:iso:3166#"+s.toUpperCase(); } else { switch (s) { case "uv" : return "http://unstats.un.org/unsd/methods/m49/m49.htm#001"; @@ -54,7 +54,7 @@ public class JurisdictionUtilities { } public static String displayJurisdiction(String s) { - return displayJurisdiction(CodeSystemUtilities.readCoding(s)); + return displayJurisdiction(CodeSystemUtilities.readCoding(getJurisdictionFromLocale(s))); } public static String displayJurisdiction(Coding c) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java index d51437863..85df79322 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/ValueSetChecker.java @@ -1,5 +1,6 @@ package org.hl7.fhir.r5.terminologies; +import java.util.ArrayList; import java.util.List; /* @@ -34,10 +35,24 @@ import java.util.List; import org.hl7.fhir.r5.terminologies.ValueSetExpander.ETooCostly; +import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.utils.EOperationOutcome; public interface ValueSetChecker { - Boolean codeInValueSet(String system, String code, List warnings) throws ETooCostly, EOperationOutcome, Exception; + public static class ValidationProcessInfo { + private TerminologyServiceErrorClass err; + private List warnings = new ArrayList<>(); + public TerminologyServiceErrorClass getErr() { + return err; + } + public void setErr(TerminologyServiceErrorClass err) { + this.err = err; + } + public List getWarnings() { + return warnings; + } + } + Boolean codeInValueSet(String system, String code, ValidationProcessInfo info) throws ETooCostly, EOperationOutcome, Exception; } \ No newline at end of file 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 ec314cc15..2f62fdb26 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 @@ -61,6 +61,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; +import org.hl7.fhir.r5.terminologies.ValueSetChecker.ValidationProcessInfo; import org.hl7.fhir.r5.terminologies.ValueSetExpander.TerminologyServiceErrorClass; import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier; @@ -129,11 +130,11 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe public ValidationResult validateCode(CodeableConcept code) throws FHIRException { // first, we validate the codings themselves List errors = new ArrayList(); - List warnings = new ArrayList(); + ValidationProcessInfo info = new ValidationProcessInfo(); if (options.getValueSetMode() != ValueSetMode.CHECK_MEMERSHIP_ONLY) { for (Coding c : code.getCoding()) { if (!c.hasSystem()) { - warnings.add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); + info.getWarnings().add(context.formatMessage(I18nConstants.CODING_HAS_NO_SYSTEM__CANNOT_VALIDATE)); } CodeSystem cs = resolveCodeSystem(c.getSystem()); ValidationResult res = null; @@ -145,7 +146,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (!res.isOk()) { errors.add(res.getMessage()); } else if (res.getMessage() != null) { - warnings.add(res.getMessage()); + info.getWarnings().add(res.getMessage()); } } } @@ -153,7 +154,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe if (valueset != null && options.getValueSetMode() != ValueSetMode.NO_MEMBERSHIP_CHECK) { Boolean result = false; for (Coding c : code.getCoding()) { - Boolean ok = codeInValueSet(c.getSystem(), c.getCode(), warnings); + Boolean ok = codeInValueSet(c.getSystem(), c.getCode(), info); if (ok == null && result == false) { result = null; } else if (ok) { @@ -162,15 +163,15 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } } if (result == null) { - warnings.add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); + info.getWarnings().add(0, context.formatMessage(I18nConstants.UNABLE_TO_CHECK_IF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); } else if (!result) { errors.add(0, context.formatMessage(I18nConstants.NONE_OF_THE_PROVIDED_CODES_ARE_IN_THE_VALUE_SET_, valueset.getUrl())); } } if (errors.size() > 0) { return new ValidationResult(IssueSeverity.ERROR, errors.toString()); - } else if (warnings.size() > 0) { - return new ValidationResult(IssueSeverity.WARNING, warnings.toString()); + } else if (info.getWarnings().size() > 0) { + return new ValidationResult(IssueSeverity.WARNING, info.getWarnings().toString()); } else { ConceptDefinitionComponent cd = new ConceptDefinitionComponent(foundCoding.getCode()); cd.setDisplay(foundCoding.getDisplay()); @@ -251,19 +252,24 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe inInclude = checkInclude(code); } - List warnings = new ArrayList<>(); + ValidationProcessInfo info = new ValidationProcessInfo(); // 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())) { - Boolean ok = codeInValueSet(system, code.getCode(), warnings); + Boolean ok = codeInValueSet(system, code.getCode(), info); if (ok == null || !ok) { if (res == null) { res = new ValidationResult((IssueSeverity) null, null); } - if (!inExpansion && !inInclude) { - if (warnings != null) { - res.setMessage("Not in value set "+valueset.getUrl()+" ("+warnings+")").setSeverity(IssueSeverity.ERROR); + if (info.getErr() != null) { + res.setErrorClass(info.getErr()); + } + if (ok == null) { + res.setMessage("Unable to check whether code is in value set "+valueset.getUrl()+": "+info.getWarnings()).setSeverity(IssueSeverity.WARNING); + } else if (!inExpansion && !inInclude) { + if (!info.getWarnings().isEmpty()) { + res.setMessage("Not in value set "+valueset.getUrl()+": "+info.getWarnings()).setSeverity(IssueSeverity.ERROR); } else { res.setMessage("Not in value set "+valueset.getUrl()).setSeverity(IssueSeverity.ERROR); } @@ -583,7 +589,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe return false; } CodeSystem cs = resolveCodeSystem(vsi.getSystem()); - if (cs != null) { + if (cs != null && cs.getContent() == CodeSystemContentMode.COMPLETE) { if (vsi.hasConcept()) { for (ConceptReferenceComponent cc : vsi.getConcept()) { @@ -598,15 +604,15 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe sys.add(vsi.getSystem()); } } - } else { - if (vsi.hasConcept()) { - for (ConceptReferenceComponent cc : vsi.getConcept()) { - boolean match = cc.getCode().equals(code); - if (match) { - sys.add(vsi.getSystem()); - } + } else if (vsi.hasConcept()) { + for (ConceptReferenceComponent cc : vsi.getConcept()) { + boolean match = cc.getCode().equals(code); + if (match) { + sys.add(vsi.getSystem()); } } + } else { + return false; } } } @@ -640,7 +646,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } @Override - public Boolean codeInValueSet(String system, String code, List warnings) throws FHIRException { + public Boolean codeInValueSet(String system, String code, ValidationProcessInfo info) throws FHIRException { if (valueset == null) { return false; } @@ -651,7 +657,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } else if (valueset.hasCompose()) { int i = 0; for (ConceptSetComponent vsi : valueset.getCompose().getInclude()) { - Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean ok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, info); i++; if (ok == null && result == false) { result = null; @@ -662,7 +668,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe } i = valueset.getCompose().getInclude().size(); for (ConceptSetComponent vsi : valueset.getCompose().getExclude()) { - Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, warnings); + Boolean nok = inComponent(vsi, i, system, code, valueset.getCompose().getInclude().size() == 1, info); i++; if (nok == null && result == false) { result = null; @@ -675,7 +681,7 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe return result; } - private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, List warnings) throws FHIRException { + private Boolean inComponent(ConceptSetComponent vsi, int vsiIndex, String system, String code, boolean only, ValidationProcessInfo info) throws FHIRException { boolean ok = true; if (vsi.hasValueSet()) { @@ -721,8 +727,9 @@ public class ValueSetCheckerSimple extends ValueSetWorker implements ValueSetChe vs.getCompose().addInclude(vsi); ValidationResult res = context.validateCode(options.noClient(), new Coding(system, code, null), vs); if (res.getErrorClass() == TerminologyServiceErrorClass.UNKNOWN || res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED || res.getErrorClass() == TerminologyServiceErrorClass.VALUESET_UNSUPPORTED) { - if (warnings != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { - warnings.add(context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system)); + if (info != null && res.getErrorClass() == TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED) { + info.getWarnings().add(context.formatMessage(I18nConstants.TERMINOLOGY_TX_SYSTEM_NOTKNOWN, system)); + info.setErr(TerminologyServiceErrorClass.CODESYSTEM_UNSUPPORTED); } return null; } 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 705a3a449..4fbafabb7 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 @@ -4503,7 +4503,18 @@ public class FHIRPathEngine { return makeBoolean(false); } } else if (ns.equals("FHIR")) { - return makeBoolean(n.equals(focus.get(0).fhirType())); + if (n.equals(focus.get(0).fhirType())) { + return makeBoolean(true); + } else { + StructureDefinition sd = worker.fetchTypeDefinition(focus.get(0).fhirType()); + while (sd != null) { + if (n.equals(sd.getType())) { + return makeBoolean(true); + } + sd = worker.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return makeBoolean(false); + } } else { return makeBoolean(false); } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java index a637231dd..ee0a1f6e2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/GraphQLSchemaGenerator.java @@ -103,6 +103,9 @@ public class GraphQLSchemaGenerator { if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) { tl.put(sd.getName(), sd); } + if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() != TypeDerivationRule.CONSTRAINT && sd.getAbstract()) { + tl.put(sd.getName(), sd); + } } writer.write("# FHIR GraphQL Schema. Version " + version + "\r\n\r\n"); writer.write("# FHIR Defined Primitive types\r\n"); @@ -290,16 +293,17 @@ public class GraphQLSchemaGenerator { } private void generateType(Map existingTypeNames, Writer writer, StructureDefinition sd, EnumSet operations) throws IOException { - if (sd.getAbstract()) { - return; - } - if (operations.contains(FHIROperationType.READ) || operations.contains(FHIROperationType.SEARCH)) { List list = new ArrayList<>(); StringBuilder b = new StringBuilder(); list.add(b); b.append("type "); b.append(sd.getName()); + StructureDefinition sdp = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + if (sdp != null) { + b.append(" implements "); + b.append(sdp.getType()); + } b.append(" {\r\n"); ElementDefinition ed = sd.getSnapshot().getElementFirstRep(); generateProperties(existingTypeNames, list, b, sd.getName(), sd, ed, "type", ""); @@ -370,7 +374,13 @@ public class GraphQLSchemaGenerator { if (suffix) b.append(Utilities.capitalize(typeDetails.getWorkingCode())); b.append(": "); - b.append(n); + if (!child.getMax().equals("1")) { + b.append("["); + b.append(n); + b.append("]"); + } else { + b.append(n); + } if (!child.getPath().endsWith(".id")) { b.append(" _"); b.append(tail(child.getPath(), suffix)); 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 bc1e7e092..803d54369 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 @@ -129,4 +129,33 @@ public class OperationOutcomeUtilities { } return res; } + + + public static OperationOutcomeIssueComponent convertToIssueSimple(ValidationMessage message, OperationOutcome op) { + OperationOutcomeIssueComponent issue = new OperationOutcome.OperationOutcomeIssueComponent(); + issue.setUserData("source.vm", message); + issue.setCode(convert(message.getType())); + + if (message.getLocation() != null) { + // message location has a fhirPath in it. We need to populate the expression + issue.addExpression(message.getLocation()); + } + if (message.getLine() >= 0 && message.getCol() >= 0) { + issue.setDiagnostics("["+message.getLine()+","+message.getCol()+"]"); + } + issue.setSeverity(convert(message.getLevel())); + CodeableConcept c = new CodeableConcept(); + c.setText(message.getMessage()); + issue.setDetails(c); + return issue; + } + + public static OperationOutcome createOutcomeSimple(List messages) { + OperationOutcome res = new OperationOutcome(); + for (ValidationMessage vm : messages) { + res.addIssue(convertToIssueSimple(vm, res)); + } + return res; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java index 60b74d0a6..8e453d43c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/ToolingExtensions.java @@ -205,6 +205,7 @@ public class ToolingExtensions { public static final String EXT_REND_MD = "http://hl7.org/fhir/StructureDefinition/rendering-markdown"; public static final String EXT_CAP_STMT_EXPECT = "http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation"; public static final String EXT_ED_HEIRARCHY = "http://hl7.org/fhir/StructureDefinition/elementdefinition-heirarchy"; + public static final String EXT_SD_DEPENDENCY = "http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies"; // in the tooling IG public static final String EXT_BINDING_ADDITIONAL = "http://hl7.org/fhir/tools/StructureDefinition/additional-binding"; diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index a80bff538..7560b736b 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index ee2461f83..43dcc2a1d 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FileFormat.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FileFormat.java new file mode 100644 index 000000000..808ea64cd --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/FileFormat.java @@ -0,0 +1,21 @@ +package org.hl7.fhir.utilities; + +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class FileFormat { + + public static boolean fileEncodingIsUtf8() { + return Charset.defaultCharset().equals(StandardCharsets.UTF_8); + } + + public static void checkCharsetAndWarnIfNotUTF8(PrintStream out) { + if (fileEncodingIsUtf8()) return; + out.println(""); + out.println("WARNING: Default file encoding is " + Charset.defaultCharset() + " which may cause unexpected results. "); + out.println(" To fix this issue, run this program with the parameter '-Dfile.encoding=UTF-8'"); + out.println(" Future releases may not be able to run at all with encoding " + Charset.defaultCharset()); + out.println(""); + } +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java index 279ab2be9..1ef695297 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/MarkDownProcessor.java @@ -64,11 +64,41 @@ public class MarkDownProcessor { } switch (dialect) { case DARING_FIREBALL : return Processor.process(source); - case COMMON_MARK : return processCommonMark(source); + case COMMON_MARK : return processCommonMark(preProcess(source)); default: throw new Error("Unknown Markdown Dialect: "+dialect.toString()+" at "+context); } } + /** + * This deals with a painful problem created by the intersection of previous publishing processes + * and the way commonmark specifies that < is handled in content. For control reasons, the FHIR specification does + * not allow raw html tags in the markdown + * + * This check finds any raw <[x] where [x] is any alpha character, and prepends \ to it so that it + * renders as a < (e.g. gets escaped in the output HTML) + * + * This is public to enable testing (not for direct use otherwise) + * + * @param source + * @return + */ + public static String preProcess(String source) { + StringBuilder b = new StringBuilder(); + for (int i = 0; i < source.length(); i++) { + char last = i > 0 ? source.charAt(i-1) : 0; + char current = source.charAt(i); + char next = i < source.length() -1 ? source.charAt(i+1) : 0; + if (current == '<' && Character.isAlphabetic(next) && last != '\\') { + b.append('\\'); + b.append(current); + } else { + b.append(current); + } + } + return b.toString(); + } + + private String processCommonMark(String source) { Set extensions = Collections.singleton(TablesExtension.create()); Parser parser = Parser.builder().extensions(extensions).build(); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 1ef878654..b626de371 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -1758,4 +1758,12 @@ public class Utilities { return text; } + public static int parseInt(String value, int def) { + if (isInteger(value)) { + return Integer.parseInt(value); + } else { + return def; + } + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java index 6c2c02c5b..6beae5a03 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/VersionUtilities.java @@ -487,7 +487,6 @@ public class VersionUtilities { res.add("ActivityDefinition"); res.add("CapabilityStatement"); - res.add("CapabilityStatement2"); res.add("ChargeItemDefinition"); res.add("Citation"); res.add("CodeSystem"); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index a2809dd73..7db656b37 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -488,6 +488,7 @@ public class I18nConstants { public static final String TYPE_SPECIFIC_CHECKS_DT_BASE64_VALID = "Type_Specific_Checks_DT_Base64_Valid"; public static final String TYPE_SPECIFIC_CHECKS_DT_BOOLEAN_VALUE = "Type_Specific_Checks_DT_Boolean_Value"; public static final String TYPE_SPECIFIC_CHECKS_DT_CODE_WS = "Type_Specific_Checks_DT_Code_WS"; + public static final String TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML = "TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML"; public static final String TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE = "Type_Specific_Checks_DT_DateTime_Reasonable"; public static final String TYPE_SPECIFIC_CHECKS_DT_DATETIME_REGEX = "Type_Specific_Checks_DT_DateTime_Regex"; public static final String TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ = "Type_Specific_Checks_DT_DateTime_TZ"; @@ -610,6 +611,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_SIGNPOST = "VALIDATION_VAL_PROFILE_SIGNPOST"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = "VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_META = "VALIDATION_VAL_PROFILE_SIGNPOST_META"; + public static final String VALIDATION_VAL_PROFILE_SIGNPOST_DEP = "VALIDATION_VAL_PROFILE_SIGNPOST_DEP"; public static final String VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = "VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM"; public static final String VALIDATION_VAL_PROFILE_SLICEORDER = "Validation_VAL_Profile_SliceOrder"; public static final String VALIDATION_VAL_PROFILE_OTHER_VERSION = "VALIDATION_VAL_PROFILE_OTHER_VERSION"; @@ -621,6 +623,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_WRONGTYPE = "Validation_VAL_Profile_WrongType"; public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String VALIDATION_VAL_UNKNOWN_PROFILE = "Validation_VAL_Unknown_Profile"; + public static final String VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED = "VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE"; public static final String VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER = "VALUESET_INCLUDE_INVALID_CONCEPT_CODE_VER"; @@ -635,6 +638,10 @@ public class I18nConstants { public static final String CODESYSTEM_SHAREABLE_MISSING_HL7 = "CODESYSTEM_SHAREABLE_MISSING_HL7"; public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = "CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7"; public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING = "CODESYSTEM_SHAREABLE_EXTRA_MISSING"; + public static final String MEASURE_SHAREABLE_MISSING = "MEASURE_SHAREABLE_MISSING"; + public static final String MEASURE_SHAREABLE_MISSING_HL7 = "MEASURE_SHAREABLE_MISSING_HL7"; + public static final String MEASURE_SHAREABLE_EXTRA_MISSING_HL7 = "MEASURE_SHAREABLE_EXTRA_MISSING_HL7"; + public static final String MEASURE_SHAREABLE_EXTRA_MISSING = "MEASURE_SHAREABLE_EXTRA_MISSING"; public static final String VALUESET_UNC_SYSTEM_WARNING = "VALUESET_UNC_SYSTEM_WARNING"; public static final String VALUESET_UNC_SYSTEM_WARNING_VER = "VALUESET_UNC_SYSTEM_WARNING_VER"; public static final String VALUESET_IMPORT_UNION_INTERSECTION = "VALUESET_IMPORT_UNION_INTERSECTION"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java index 47e5e27a8..84fc35ba4 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java @@ -686,7 +686,7 @@ public class JsonTrackingParser { } public static void write(JsonObject json, File file) throws IOException { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); + Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); String jcnt = gson.toJson(json); TextFile.stringToFile(jcnt, file); } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 654b93cc4..75985cd76 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -236,6 +236,7 @@ Validation_VAL_Profile_Unknown = Profile reference ''{0}'' has not been checked 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 VALIDATION_VAL_PROFILE_UNKNOWN_ERROR = Profile reference ''{0}'' has not been checked because it is unknown, and fetching it resulted in the error {1} Validation_VAL_Unknown_Profile = Unknown profile {0} +VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED = Profile {1} identifies {2} as a dependency (using the extension http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies), but this profile could not be found XHTML_XHTML_Attribute_Illegal = Illegal attribute name in the XHTML (''{0}'' on ''{1}'') XHTML_XHTML_Element_Illegal = Illegal element name in the XHTML (''{0}'') XHTML_XHTML_NS_InValid = Wrong namespace on the XHTML (''{0}'', should be ''{1}'') @@ -384,7 +385,7 @@ Error_parsing_JSON_ = Error parsing JSON: {0} Node_type__is_not_allowed = Node type {0} is not allowed CDATA_is_not_allowed = CDATA is not allowed Undefined_element_ = Undefined element ''{0}'' -Undefined_attribute__on__for_type__properties__ = Undefined attribute ''@{0}'' on {1} for type {2} (properties = {3}) +Undefined_attribute__on__for_type__properties__ = Undefined attribute ''@{0}'' on {1} for type {2} Text_should_not_be_present = Text should not be present (''{0}'') Wrong_namespace__expected_ = Wrong namespace - expected ''{0}'' Element_must_have_some_content = Element must have some content @@ -536,6 +537,7 @@ VALIDATION_VAL_GLOBAL_PROFILE_UNKNOWN = Global Profile reference ''{0}'' from IG VALIDATION_VAL_PROFILE_SIGNPOST_BASE = Validate resource against profile VALIDATION_VAL_PROFILE_SIGNPOST = Validate resource against profile {0} VALIDATION_VAL_PROFILE_SIGNPOST_META = Validate resource against profile {0} (per meta) +VALIDATION_VAL_PROFILE_SIGNPOST_DEP = Validate resource against profile {0} (per http://hl7.org/fhir/StructureDefinition/structuredefinition-dependencies in {1}) VALIDATION_VAL_PROFILE_SIGNPOST_BUNDLE_PARAM = Validate resource against profile {0} - provided as bundle param VALIDATION_VAL_PROFILE_SIGNPOST_GLOBAL = Validate resource against profile {0} - a global profile in {1} ERROR_GENERATING_SNAPSHOT = Error generating Snapshot: {0} (this usually arises from a problem in the differential) @@ -726,7 +728,13 @@ VALUESET_SHAREABLE_MISSING = The ShareableValueSet profile says that the {0} ele VALUESET_SHAREABLE_EXTRA_MISSING = The ShareableValueSet profile recommends that the {0} element is populated, but it is not present. Published value sets SHOULD conform to the ShareableValueSet profile VALUESET_SHAREABLE_MISSING_HL7 = The ShareableValueSet profile says that the {0} element is mandatory, but it is not found. HL7 Published value sets SHALL conform to the ShareableValueSet profile VALUESET_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableValueSet profile recommends that the {0} element is populated, but it is not found. HL7 Published value sets SHALL conform to the ShareableValueSet profile -CODESYSTEM_SHAREABLE_MISSING = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not present. Published value sets SHOULD conform to the ShareableCodeSystem profile -CODESYSTEM_SHAREABLE_EXTRA_MISSING = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not present. Published value sets SHOULD conform to the ShareableCodeSystem profile -CODESYSTEM_SHAREABLE_MISSING_HL7 = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not found. HL7 Published value sets SHALL conform to the ShareableCodeSystem profile -CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not found. HL7 Published value sets SHALL conform to the ShareableCodeSystem profile +CODESYSTEM_SHAREABLE_MISSING = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not present. Published code systems SHOULD conform to the ShareableCodeSystem profile +CODESYSTEM_SHAREABLE_EXTRA_MISSING = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not present. Published code systems SHOULD conform to the ShareableCodeSystem profile +CODESYSTEM_SHAREABLE_MISSING_HL7 = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile +CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile +MEASURE_SHAREABLE_MISSING = The ShareableMeasure profile says that the {0} element is mandatory, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile +MEASURE_SHAREABLE_EXTRA_MISSING = The ShareableMeasure profile recommends that the {0} element is populated, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile +MEASURE_SHAREABLE_MISSING_HL7 = The ShareableMeasure profile says that the {0} element is mandatory, but it is not found. HL7 Published measures SHALL conform to the ShareableMeasure profile +MEASURE_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableMeasure profile recommends that the {0} element is populated, but it is not found. HL7 Published measures SHALL conform to the ShareableMeasure profile +TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML = The markdown contains content that appears to be an embedded HTML tag starting at ''{0}''. This will (or SHOULD) be escaped by the presentation layer. The content should be checked to confirm that this is the desired behaviour + diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FileFormatTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FileFormatTest.java new file mode 100644 index 000000000..c79625399 --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/FileFormatTest.java @@ -0,0 +1,35 @@ +package org.hl7.fhir.utilities; + +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +public class FileFormatTest { + @Test + public void testCurrentFileFormat() throws IOException { + ByteArrayOutputStream bo = new ByteArrayOutputStream(); + + FileFormat.checkCharsetAndWarnIfNotUTF8(new PrintStream(bo)); + + bo.flush(); + String allWrittenLines = new String(bo.toByteArray()); + + assertAWarningIsGivenWhenNotUTF8(allWrittenLines); + } + + private static void assertAWarningIsGivenWhenNotUTF8(String allWrittenLines) { + if (Charset.defaultCharset().equals(StandardCharsets.UTF_8)) { + assertEquals(0, allWrittenLines.length()); + } else { + assertThat(allWrittenLines, containsString("WARNING")); + } + } +} diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/MarkdownPreprocessorTesting.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/MarkdownPreprocessorTesting.java new file mode 100644 index 000000000..cb3249a60 --- /dev/null +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/MarkdownPreprocessorTesting.java @@ -0,0 +1,30 @@ +package org.hl7.fhir.utilities.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.hl7.fhir.utilities.MarkDownProcessor; +import org.junit.jupiter.api.Test; + +public class MarkdownPreprocessorTesting { + + @Test + public void testSimple() throws IOException { + assertEquals(MarkDownProcessor.preProcess("1 < 2"), "1 < 2"); + } + + @Test + public void testHTML() throws IOException { + assertEquals(MarkDownProcessor.preProcess(""), "\\"); + assertEquals(MarkDownProcessor.preProcess("\\"), "\\"); + } + + + @Test + public void testBorder() throws IOException { + assertEquals(MarkDownProcessor.preProcess("<>"), "<>"); + assertEquals(MarkDownProcessor.preProcess("><"), "><"); + } + +} diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 1213a751d..5d5332049 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 72f2fe96f..854a27fc8 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT ../pom.xml @@ -197,7 +197,7 @@ com.fasterxml.jackson.core jackson-databind - 2.12.6.1 + 2.14.0-rc1 org.thymeleaf diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 33c051671..76fbf57ea 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -53,6 +53,7 @@ import org.hl7.fhir.utilities.npm.ToolsVersion; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.xhtml.XhtmlComposer; import org.hl7.fhir.validation.BaseValidator.ValidationControl; +import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck; import org.hl7.fhir.validation.cli.services.IPackageInstaller; import org.hl7.fhir.validation.cli.utils.*; import org.hl7.fhir.validation.instance.InstanceValidator; @@ -160,6 +161,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP @Getter @Setter private boolean allowExampleUrls; @Getter @Setter private boolean showMessagesFromReferences; @Getter @Setter private boolean doImplicitFHIRPathStringConversion; + @Getter @Setter private HtmlInMarkdownCheck htmlInMarkdownCheck; @Getter @Setter private Locale locale; @Getter @Setter private List igs = new ArrayList<>(); @Getter @Setter private List extensionDomains = new ArrayList<>(); @@ -625,6 +627,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IValidationP validator.getValidationControl().putAll(validationControl); validator.setQuestionnaireMode(questionnaireMode); validator.setLevel(level); + validator.setHtmlInMarkdownCheck(htmlInMarkdownCheck); validator.setNoUnicodeBiDiControlChars(noUnicodeBiDiControlChars); validator.setDoImplicitFHIRPathStringConversion(doImplicitFHIRPathStringConversion); if (format == FhirFormat.SHC) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index d4fa6951c..46abaa74d 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE. import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; +import org.hl7.fhir.utilities.FileFormat; import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -154,6 +155,8 @@ public class ValidatorCli { CliContext cliContext = Params.loadCliContext(args); + FileFormat.checkCharsetAndWarnIfNotUTF8(System.out); + if (shouldDisplayHelpToUser(args)) { Display.displayHelpDetails(); } else if (Params.hasParam(args, Params.COMPARE)) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java index ec5df3e11..af6d73837 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/CliContext.java @@ -48,6 +48,8 @@ public class CliContext { private boolean wantInvariantsInMessages = false; @JsonProperty("doImplicitFHIRPathStringConversion") private boolean doImplicitFHIRPathStringConversion = false; + @JsonProperty("htmlInMarkdownCheck") + private HtmlInMarkdownCheck htmlInMarkdownCheck = HtmlInMarkdownCheck.WARNING; @JsonProperty("map") private String map = null; @@ -248,6 +250,16 @@ public class CliContext { this.doImplicitFHIRPathStringConversion = doImplicitFHIRPathStringConversion; } + @JsonProperty("htmlInMarkdownCheck") + public HtmlInMarkdownCheck getHtmlInMarkdownCheck() { + return htmlInMarkdownCheck; + } + + @JsonProperty("htmlInMarkdownCheck") + public void setHtmlInMarkdownCheck(HtmlInMarkdownCheck htmlInMarkdownCheck) { + this.htmlInMarkdownCheck = htmlInMarkdownCheck; + } + @JsonProperty("locale") public String getLanguageCode() { return locale; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/HtmlInMarkdownCheck.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/HtmlInMarkdownCheck.java new file mode 100644 index 000000000..08585daa6 --- /dev/null +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/model/HtmlInMarkdownCheck.java @@ -0,0 +1,30 @@ +package org.hl7.fhir.validation.cli.model; + +import org.hl7.fhir.utilities.Utilities; + +public enum HtmlInMarkdownCheck { + NONE, + WARNING, + ERROR; + + public static boolean isValidCode(String q) { + return fromCode(q) != null; + } + + public static HtmlInMarkdownCheck fromCode(String q) { + if (Utilities.noString(q)) { + return null; + } + q = q.toLowerCase(); + if (Utilities.existsInList(q, "n", "none", "ignore", "i")) { + return NONE; + } + if (Utilities.existsInList(q, "w", "warning", "warnings", "warn")) { + return WARNING; + } + if (Utilities.existsInList(q, "e", "error", "errors", "err")) { + return ERROR; + } + return null; + } +} 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 457bd4b35..0faa41b41 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 @@ -372,6 +372,7 @@ public class ValidationService { validator.setAssumeValidRestReferences(cliContext.isAssumeValidRestReferences()); validator.setShowMessagesFromReferences(cliContext.isShowMessagesFromReferences()); validator.setDoImplicitFHIRPathStringConversion(cliContext.isDoImplicitFHIRPathStringConversion()); + validator.setHtmlInMarkdownCheck(cliContext.getHtmlInMarkdownCheck()); validator.setNoExtensibleBindingMessages(cliContext.isNoExtensibleBindingMessages()); validator.setNoUnicodeBiDiControlChars(cliContext.isNoUnicodeBiDiControlChars()); validator.setNoInvariantChecks(cliContext.isNoInvariants()); 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 e80378cc0..17e7d0800 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 @@ -5,6 +5,7 @@ import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; import org.hl7.fhir.r5.utils.validation.BundleValidationRule; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.validation.cli.model.CliContext; +import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck; import java.io.File; import java.util.Arrays; @@ -69,7 +70,8 @@ public class Params { public static final String ALLOW_EXAMPLE_URLS = "-allow-example-urls"; public static final String OUTPUT_STYLE = "-output-style"; public static final String DO_IMPLICIT_FHIRPATH_STRING_CONVERSION = "-implicit-fhirpath-string-conversions"; - private static final Object JURISDICTION = "-jurisdiction"; + public static final String JURISDICTION = "-jurisdiction"; + public static final String HTML_IN_MARKDOWN = "-html-in-markdown"; public static final String RUN_TESTS = "-run-tests"; @@ -176,6 +178,17 @@ public class Params { cliContext.setShowMessagesFromReferences(true); } else if (args[i].equals(DO_IMPLICIT_FHIRPATH_STRING_CONVERSION)) { cliContext.setDoImplicitFHIRPathStringConversion(true); + } else if (args[i].equals(HTML_IN_MARKDOWN)) { + if (i + 1 == args.length) + throw new Error("Specified "+HTML_IN_MARKDOWN+" without indicating mode"); + else { + String q = args[++i]; + if (!HtmlInMarkdownCheck.isValidCode(q)) { + throw new Error("Specified "+HTML_IN_MARKDOWN+" with na invalid code - must be ignore, warning, or error"); + } else { + cliContext.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.fromCode(q)); + } + } } else if (args[i].equals(LOCALE)) { if (i + 1 == args.length) { throw new Error("Specified -locale without indicating locale"); 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 57f0f2074..27b1d119e 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 @@ -42,6 +42,8 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -142,6 +144,7 @@ import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.XVerExtensionManager; import org.hl7.fhir.r5.utils.validation.constants.*; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.SIDUtilities; import org.hl7.fhir.utilities.SimpleTimeTracker; import org.hl7.fhir.utilities.TimeTracker; @@ -159,9 +162,12 @@ import org.hl7.fhir.utilities.validation.ValidationOptions; import org.hl7.fhir.utilities.xhtml.NodeType; import org.hl7.fhir.utilities.xhtml.XhtmlNode; import org.hl7.fhir.validation.BaseValidator; +import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck; import org.hl7.fhir.validation.cli.utils.QuestionnaireMode; import org.hl7.fhir.validation.cli.utils.ValidationLevel; import org.hl7.fhir.validation.instance.InstanceValidator.CanonicalResourceLookupResult; +import org.hl7.fhir.validation.instance.InstanceValidator.CanonicalTypeSorter; +import org.hl7.fhir.validation.instance.InstanceValidator.StructureDefinitionSorterByUrl; import org.hl7.fhir.validation.instance.type.BundleValidator; import org.hl7.fhir.validation.instance.type.CodeSystemValidator; import org.hl7.fhir.validation.instance.type.MeasureValidator; @@ -199,6 +205,25 @@ import com.google.gson.JsonObject; */ public class InstanceValidator extends BaseValidator implements IResourceValidator { + + public class StructureDefinitionSorterByUrl implements Comparator { + + @Override + public int compare(StructureDefinition o1, StructureDefinition o2) { + return o1.getUrl().compareTo(o2.getUrl()); + } + + } + + public class CanonicalTypeSorter implements Comparator { + + @Override + public int compare(CanonicalType o1, CanonicalType o2) { + return o1.getValue().compareTo(o2.getValue()); + } + + } + public class CanonicalResourceLookupResult { private CanonicalResource resource; @@ -387,6 +412,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat private boolean noCheckAggregation; private boolean wantCheckSnapshotUnchanged; private boolean noUnicodeBiDiControlChars; + private HtmlInMarkdownCheck htmlInMarkdownCheck; private List igs = new ArrayList<>(); private List extensionDomains = new ArrayList(); @@ -792,15 +818,31 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat setParents(element); long t = System.nanoTime(); + NodeStack stack = new NodeStack(context, path, element, validationLanguage); if (profiles == null || profiles.isEmpty()) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds(), null); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null); } else { + int i = 0; + while (i < profiles.size()) { + StructureDefinition sd = profiles.get(i); + if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) { + StructureDefinition dep = context.fetchResource( StructureDefinition.class, ext.getValue().primitiveValue()); + if (dep == null) { + warning(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), sd.getUrl()); + } else if (!profiles.contains(dep)) { + profiles.add(dep); + } + } + } + i++; + } for (StructureDefinition defn : profiles) { - validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, new NodeStack(context, path, element, validationLanguage).resetIds(), null); + validateResource(new ValidatorHostContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null); } } if (hintAboutNonMustSupport) { - checkElementUsage(errors, element, new NodeStack(context, path, element, validationLanguage)); + checkElementUsage(errors, element, stack); } errors.removeAll(messagesToRemove); timeTracker.overall(t); @@ -1191,7 +1233,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkBindings(errors, path, element, stack, valueset, nextCoding); } } - timeTracker.tx(t, "vc "+DataRenderer.display(context, cc)); + timeTracker.tx(t, "vc "+cc.toString()); } } } catch (Exception e) { @@ -1666,6 +1708,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat txHint(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NOVALID_14, describeReference(binding.getValueSet(), valueset), getErrorMessage(vr.getMessage()), theSystem+"#"+theCode); } } + } else if (vr != null && vr.getMessage() != null) { + txWarning(errors, vr.getTxLink(), IssueType.CODEINVALID, element.line(), element.col(), path, false, vr.getMessage()); } } catch (Exception e) { if (STACK_TRACE) e.printStackTrace(); @@ -2134,7 +2178,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!"xhtml".equals(type)) { if (securityChecks) { rule(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR); - } else { + } else if (!"markdown".equals(type)){ hint(errors, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_WARNING); } } @@ -2318,6 +2362,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkPrimitiveBinding(hostContext, errors, path, type, context, e, profile, node); } + if (type.equals("markdown") && htmlInMarkdownCheck != HtmlInMarkdownCheck.NONE) { + String raw = e.primitiveValue(); + String processed = MarkDownProcessor.preProcess(raw); + if (!raw.equals(processed)) { + int i = 0; + while (i < raw.length() && raw.charAt(1) == processed.charAt(i)) { + i++; + } + warningOrError(htmlInMarkdownCheck == HtmlInMarkdownCheck.ERROR, errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_MARKDOWN_HTML, raw.subSequence(i, 2)); + } + } if (type.equals("xhtml")) { XhtmlNode xhtml = e.getXhtml(); if (xhtml != null) { // if it is null, this is an error already noted in the parsers @@ -3226,7 +3281,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return true; } - private String asListByUrl(Collection list) { + private String asListByUrl(Collection coll) { + List list = new ArrayList<>(); + list.addAll(coll); + Collections.sort(list, new StructureDefinitionSorterByUrl()); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); for (StructureDefinition sd : list) { b.append(sd.getUrl()); @@ -3234,7 +3292,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return b.toString(); } - private String asList(Collection list) { + private String asList(Collection coll) { + List list = new ArrayList<>(); + list.addAll(coll); + Collections.sort(list, new CanonicalTypeSorter()); CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); for (CanonicalType c : list) { b.append(c.getValue()); @@ -3670,7 +3731,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } private String getErrorMessage(String message) { - return message != null ? " (error message = " + message + ")" : ""; + return message != null ? " (error message = " + message + ")" : ""; } public boolean isSuppressLoincSnomedMessages() { @@ -4474,28 +4535,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (!fetcher.fetchesCanonicalResource(this, profile.primitiveValue())) { warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_NOT_POLICY, profile.primitiveValue()); } else { - sd = null; - String url = profile.primitiveValue(); - CanonicalResourceLookupResult cr = crLookups.get(url); - if (cr != null) { - if (cr.error != null) { - warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, url, cr.error); - } else { - sd = (StructureDefinition) cr.resource; - } - } else { - try { - sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url); - crLookups.put(url, new CanonicalResourceLookupResult(sd)); - } catch (Exception e) { - if (STACK_TRACE) { e.printStackTrace(); } - crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage())); - warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); - } - if (sd != null) { - context.cacheResource(sd); - } - } + sd = lookupProfileReference(errors, element, stack, i, profile, sd); } } if (sd != null) { @@ -4510,6 +4550,27 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (pctOwned) { pct.done(); } + if (sd.hasExtension(ToolingExtensions.EXT_SD_DEPENDENCY)) { + for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_DEPENDENCY)) { + StructureDefinition sdi = context.fetchResource(StructureDefinition.class, ext.getValue().primitiveValue()); + if (sdi == null) { + warning(errors, IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_DEPENDS_NOT_RESOLVED, ext.getValue().primitiveValue(), sd.getUrl()); + } else { + if (crumbTrails) { + element.addMessage(signpost(errors, IssueType.INFORMATIONAL, element.line(), element.col(), stack.getLiteralPath(), I18nConstants.VALIDATION_VAL_PROFILE_SIGNPOST_DEP, sdi.getUrl(), sd.getUrl())); + } + stack.resetIds(); + if (pctOwned) { + pct = new PercentageTracker(resource.countDescendents(), resource.fhirType(), sdi.getUrl(), logProgress); + } + startInner(hostContext, errors, resource, element, sdi, stack, false, pct); + if (pctOwned) { + pct.done(); + } + + } + } + } } } } @@ -4540,6 +4601,32 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat // System.out.println("start: "+(System.currentTimeMillis()-st)+" ("+resource.fhirType()+")"); } + private StructureDefinition lookupProfileReference(List errors, Element element, NodeStack stack, + int i, Element profile, StructureDefinition sd) { + String url = profile.primitiveValue(); + CanonicalResourceLookupResult cr = crLookups.get(url); + if (cr != null) { + if (cr.error != null) { + warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, url, cr.error); + } else { + sd = (StructureDefinition) cr.resource; + } + } else { + try { + sd = (StructureDefinition) fetcher.fetchCanonicalResource(this, url); + crLookups.put(url, new CanonicalResourceLookupResult(sd)); + } catch (Exception e) { + if (STACK_TRACE) { e.printStackTrace(); } + crLookups.put(url, new CanonicalResourceLookupResult(e.getMessage())); + warning(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath() + ".meta.profile[" + i + "]", false, I18nConstants.VALIDATION_VAL_PROFILE_UNKNOWN_ERROR, profile.primitiveValue(), e.getMessage()); + } + if (sd != null) { + context.cacheResource(sd); + } + } + return sd; + } + // private void plog(String msg) { // long n = System.currentTimeMillis(); // String elapsed = Utilities.padLeft(Long.toString(n-start), ' ', 5); @@ -4638,9 +4725,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } else if (element.getType().equals("QuestionnaireResponse")) { new QuestionnaireValidator(context, myEnableWhenEvaluator, fpe, timeTracker, questionnaireMode, xverManager, jurisdiction).validateQuestionannaireResponse(hostContext, errors, element, stack); } else if (element.getType().equals("Measure")) { - new MeasureValidator(context, timeTracker, xverManager, jurisdiction).validateMeasure(hostContext, errors, element, stack); + new MeasureValidator(context, timeTracker, xverManager, jurisdiction, this).validateMeasure(hostContext, errors, element, stack); } else if (element.getType().equals("MeasureReport")) { - new MeasureValidator(context, timeTracker, xverManager, jurisdiction).validateMeasureReport(hostContext, errors, element, stack); + new MeasureValidator(context, timeTracker, xverManager, jurisdiction, this).validateMeasureReport(hostContext, errors, element, stack); } else if (element.getType().equals("CapabilityStatement")) { validateCapabilityStatement(errors, element, stack); } else if (element.getType().equals("CodeSystem")) { @@ -5677,7 +5764,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat boolean ok; try { long t = System.nanoTime(); - ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); + ok = fpe.evaluateToBoolean(hostContext, resource, hostContext.getRootResource(), element, n); timeTracker.fpe(t); msg = fpe.forLog(); } catch (Exception ex) { @@ -6025,6 +6112,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat this.noUnicodeBiDiControlChars = noUnicodeBiDiControlChars; } + + + public HtmlInMarkdownCheck getHtmlInMarkdownCheck() { + return htmlInMarkdownCheck; + } + + public void setHtmlInMarkdownCheck(HtmlInMarkdownCheck htmlInMarkdownCheck) { + this.htmlInMarkdownCheck = htmlInMarkdownCheck; + } + public Coding getJurisdiction() { return jurisdiction; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java index c89ca492f..877e4b2d3 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java @@ -84,7 +84,9 @@ public class CodeSystemValidator extends BaseValidator { } } - checkShareableCodeSystem(errors, cs, stack); + if (!stack.isContained()) { + checkShareableCodeSystem(errors, cs, stack); + } } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java index b85cf50a0..0ec0198c7 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/MeasureValidator.java @@ -37,17 +37,20 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.Source; import org.hl7.fhir.utilities.xml.XMLUtil; import org.hl7.fhir.validation.BaseValidator; import org.hl7.fhir.validation.TimeTracker; +import org.hl7.fhir.validation.instance.InstanceValidator; import org.hl7.fhir.validation.instance.utils.NodeStack; import org.hl7.fhir.validation.instance.utils.ValidatorHostContext; import org.w3c.dom.Document; public class MeasureValidator extends BaseValidator { - public MeasureValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager, Coding jurisdiction) { + private InstanceValidator parent; + public MeasureValidator(IWorkerContext context, TimeTracker timeTracker, XVerExtensionManager xverManager, Coding jurisdiction, InstanceValidator parent) { super(context, xverManager); source = Source.InstanceValidator; this.timeTracker = timeTracker; this.jurisdiction = jurisdiction; + this.parent = parent; } @@ -105,8 +108,41 @@ public class MeasureValidator extends BaseValidator { c++; } } + if (!stack.isContained()) { + checkShareableMeasure(errors, element, stack); + } } + + private void checkShareableMeasure(List errors, Element cs, NodeStack stack) { + if (parent.isForPublication()) { + if (isHL7(cs)) { + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "url"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "version"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "title"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.MEASURE_SHAREABLE_EXTRA_MISSING_HL7, "name"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "status"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "experimental"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "description"); + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "content"); + if (!"supplement".equals(cs.getChildValue("content"))) { + rule(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.MEASURE_SHAREABLE_MISSING_HL7, "caseSensitive"); + } + } else { + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.MEASURE_SHAREABLE_MISSING, "url"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.MEASURE_SHAREABLE_MISSING, "version"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.MEASURE_SHAREABLE_MISSING, "title"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.MEASURE_SHAREABLE_EXTRA_MISSING, "name"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.MEASURE_SHAREABLE_MISSING, "status"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.MEASURE_SHAREABLE_MISSING, "experimental"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.MEASURE_SHAREABLE_MISSING, "description"); + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("content"), I18nConstants.MEASURE_SHAREABLE_MISSING, "content"); + if (!"supplement".equals(cs.getChildValue("content"))) { + warning(errors, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("caseSensitive"), I18nConstants.MEASURE_SHAREABLE_MISSING, "caseSensitive"); + } + } + } + } private void validateMeasureCriteria(ValidatorHostContext hostContext, List errors, MeasureContext mctxt, Element crit, NodeStack nsc) { String mimeType = crit.getChildValue("language"); if (!Utilities.noString(mimeType)) { // that would be an error elsewhere diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index 6522375f9..8af109941 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -115,8 +115,9 @@ public class StructureDefinitionValidator extends BaseValidator { for (Element snapshot : snapshots) { validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd); } + } - + private void validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd) { List elements = elementList.getChildrenByName("element"); int cc = 0; diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java index 0c9144cf3..44691b6d5 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/ValueSetValidator.java @@ -61,7 +61,9 @@ public class ValueSetValidator extends BaseValidator { cc++; } } - checkShareableValueSet(errors, vs, stack); + if (!stack.isContained()) { + checkShareableValueSet(errors, vs, stack); + } } private void checkShareableValueSet(List errors, Element vs, NodeStack stack) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java index be2590646..979ba0e16 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java @@ -45,6 +45,13 @@ public class FHIRPathExpressionFixer { return ("name.exists() implies name.matches('[A-Z]([A-Za-z0-9_]){0,254}')"); } + // R5 ballot + if (expr.equals("url.matches('([^|#])*')")) { + return ("$this.matches('([^|#])*')"); + } + + + // clarification in FHIRPath spec if ("eld-19".equals(key)) { diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java new file mode 100644 index 000000000..a4dbf4683 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/r5/test/GeneralTests.java @@ -0,0 +1,36 @@ +package org.hl7.fhir.r5.test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import org.hl7.fhir.r5.elementmodel.ParserBase.ValidationPolicy; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.test.utils.TestingUtilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.junit.jupiter.api.Test; + + +public class GeneralTests { + + @Test + void testXMLParse() throws IOException { + System.out.println(System.getProperty("java.vm.name")); + InputStream stream = TestingUtilities.loadTestResourceStream("validator", + "xml_v10.xml"); + org.hl7.fhir.r5.elementmodel.XmlParser xp = new org.hl7.fhir.r5.elementmodel.XmlParser(TestingUtilities.getSharedWorkerContext()); + xp.setAllowXsiLocation(true); + List errorList = new ArrayList<>(); + xp.setupValidation(ValidationPolicy.EVERYTHING, errorList); + try { + Object resource = xp.parse(stream); + } catch (Exception e) { + e.printStackTrace(); + } + for (ValidationMessage message : errorList) { + System.out.println(message.getMessage()); + } + } + +} diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index 5ef3f6758..24ec6b3f1 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -1,6 +1,10 @@ package org.hl7.fhir.validation.tests; +import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.ByteArrayInputStream; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -22,6 +26,8 @@ import org.hl7.fhir.convertors.factory.VersionConvertorFactory_10_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_14_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_30_50; import org.hl7.fhir.convertors.factory.VersionConvertorFactory_40_50; +import org.hl7.fhir.r5.model.OperationOutcome; +import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent; import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; @@ -31,6 +37,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.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.JsonParser; import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.model.Base; @@ -46,6 +53,7 @@ import org.hl7.fhir.r5.model.ValueSet; import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.r5.utils.FHIRPathEngine; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; +import org.hl7.fhir.r5.utils.OperationOutcomeUtilities; import org.hl7.fhir.r5.utils.validation.IResourceValidator; import org.hl7.fhir.r5.utils.validation.IValidationPolicyAdvisor; import org.hl7.fhir.r5.utils.validation.constants.BestPracticeWarningLevel; @@ -56,17 +64,20 @@ import org.hl7.fhir.r5.utils.validation.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.validation.constants.ContainedReferenceValidationPolicy; import org.hl7.fhir.r5.utils.validation.constants.ReferenceValidationPolicy; import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; +import org.hl7.fhir.utilities.json.JsonTrackingParser; import org.hl7.fhir.utilities.json.JsonUtilities; import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.validation.ValidationMessage; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.validation.IgLoader; import org.hl7.fhir.validation.ValidationEngine; +import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck; import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher; import org.hl7.fhir.validation.instance.InstanceValidator; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Test; +import org.junit.jupiter.api.Assertions; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -82,6 +93,7 @@ import com.google.gson.JsonObject; public class ValidationTests implements IEvaluationContext, IValidatorResourceFetcher, IValidationPolicyAdvisor { public final static boolean PRINT_OUTPUT_TO_CONSOLE = true; + private static final boolean BUILD_NEW = false; @Parameters(name = "{index}: id {0}") public static Iterable data() throws IOException { @@ -128,7 +140,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe long setup = System.nanoTime(); this.name = name; - System.out.println("---- " + name + " ----------------------------------------------------------------"); + System.out.println("---- " + name + " ---------------------------------------------------------------- ("+System.getProperty("java.vm.name")+")"); System.out.println("** Core: "); String txLog = null; if (content.has("txLog")) { @@ -254,7 +266,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe if (content.has("security-checks")) { val.setSecurityChecks(content.get("security-checks").getAsBoolean()); } - + if (content.has("noHtmlInMarkdown")) { + val.setHtmlInMarkdownCheck(HtmlInMarkdownCheck.ERROR); + } 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)); @@ -333,6 +347,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe checkOutcomes(errorsLogical, logical, "logical", name); } logger.verifyHasNoRequests(); + if (BUILD_NEW) { + JsonTrackingParser.write(manifest, new File(Utilities.path("[tmp]", "validator", "manifest.new.json"))); + } } @@ -400,57 +417,139 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe } } - private void checkOutcomes(List errors, JsonObject focus, String profile, String name) { + private void checkOutcomes(List errors, JsonObject focus, String profile, String name) throws IOException { JsonObject java = focus.getAsJsonObject("java"); - int ec = 0; - int wc = 0; - int hc = 0; - List errLocs = new ArrayList<>(); - for (ValidationMessage vm : errors) { - if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) { - ec++; - if (PRINT_OUTPUT_TO_CONSOLE) { - System.out.println(vm.getDisplay()); - } - errLocs.add(vm.getLocation()); - } - if (vm.getLevel() == IssueSeverity.WARNING) { - wc++; - if (PRINT_OUTPUT_TO_CONSOLE) { - System.out.println(vm.getDisplay()); - } - } - if (vm.getLevel() == IssueSeverity.INFORMATION) { - hc++; - if (PRINT_OUTPUT_TO_CONSOLE) { - System.out.println(vm.getDisplay()); - } + OperationOutcome goal = java.has("outcome") ? (OperationOutcome) new JsonParser().parse(java.getAsJsonObject("outcome")) : new OperationOutcome(); + OperationOutcome actual = OperationOutcomeUtilities.createOutcomeSimple(errors); + actual.setText(null); + String json = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(actual); + + List fails = new ArrayList<>(); + + Map map = new HashMap<>(); + for (OperationOutcomeIssueComponent issGoal : goal.getIssue()) { + OperationOutcomeIssueComponent issActual = findMatchingIssue(actual, issGoal); + if (issActual == null) { + fails.add("Expected Issue not found: "+issGoal.toString()); + } else { + map.put(issActual, issGoal); } } - if (!TestingUtilities.getSharedWorkerContext(version).isNoTerminologyServer() || !focus.has("tx-dependent")) { - Assert.assertEquals("Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".", java.get("errorCount").getAsInt(), ec); + for (OperationOutcomeIssueComponent issActual : actual.getIssue()) { + if (PRINT_OUTPUT_TO_CONSOLE) { + System.out.println(issActual.toString()); + } + OperationOutcomeIssueComponent issGoal = map.get(issActual); + if (issGoal == null) { + fails.add("Unexpected Issue found: "+issActual.toString()); + } + } + if (goal.getIssue().size() != actual.getIssue().size() && fails.isEmpty()) { + fails.add("Issue count mismatch (check for duplicate error messages)"); + } + + if (fails.size() > 0) { + for (String s : fails) { + System.out.println(s); + } + System.out.println(""); + System.out.println("========================================================"); + System.out.println(""); + System.out.println(json); + System.out.println(""); + System.out.println("========================================================"); + System.out.println(""); + Assertions.fail(fails.toString()); + } +// int ec = 0; +// int wc = 0; +// int hc = 0; +// List errLocs = new ArrayList<>(); +// for (ValidationMessage vm : errors) { +// if (vm.getLevel() == IssueSeverity.FATAL || vm.getLevel() == IssueSeverity.ERROR) { +// ec++; +// if (PRINT_OUTPUT_TO_CONSOLE) { +// System.out.println(vm.getDisplay()); +// } +// errLocs.add(vm.getLocation()); +// } +// if (vm.getLevel() == IssueSeverity.WARNING) { +// wc++; +// if (PRINT_OUTPUT_TO_CONSOLE) { +// System.out.println(vm.getDisplay()); +// } +// } +// if (vm.getLevel() == IssueSeverity.INFORMATION) { +// hc++; +// if (PRINT_OUTPUT_TO_CONSOLE) { +// System.out.println(vm.getDisplay()); +// } +// } +// } +// if (!TestingUtilities.getSharedWorkerContext(version).isNoTerminologyServer() || !focus.has("tx-dependent")) { +// Assert.assertEquals("Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".", java.get("errorCount").getAsInt(), ec); +// if (java.has("warningCount")) { +// Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".", java.get("warningCount").getAsInt(), wc); +// } +// if (java.has("infoCount")) { +// Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".", java.get("infoCount").getAsInt(), hc); +// } +// } +// if (java.has("error-locations")) { +// JsonArray el = java.getAsJsonArray("error-locations"); +// Assert.assertEquals( "locations count is not correct", errLocs.size(), el.size()); +// for (int i = 0; i < errLocs.size(); i++) { +// Assert.assertEquals("Location should be " + el.get(i).getAsString() + ", but was " + errLocs.get(i), errLocs.get(i), el.get(i).getAsString()); +// } +// } + if (BUILD_NEW) { + if (java.has("output")) { + java.remove("output"); + } + if (java.has("error-locations")) { + java.remove("error-locations"); + } if (java.has("warningCount")) { - Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".", java.get("warningCount").getAsInt(), wc); + java.remove("warningCount"); } if (java.has("infoCount")) { - Assert.assertEquals( "Test " + name + (profile == null ? "" : " profile: "+ profile) + ": Expected " + Integer.toString(java.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".", java.get("infoCount").getAsInt(), hc); + java.remove("infoCount"); + } + if (java.has("errorCount")) { + java.remove("errorCount"); + } + if (java.has("outcome")) { + java.remove("outcome"); + } + if (actual.hasIssue()) { + JsonObject oj = JsonTrackingParser.parse(json, null); + java.add("outcome", oj); } } - if (java.has("error-locations")) { - JsonArray el = java.getAsJsonArray("error-locations"); - Assert.assertEquals( "locations count is not correct", errLocs.size(), el.size()); - for (int i = 0; i < errLocs.size(); i++) { - Assert.assertEquals("Location should be " + el.get(i).getAsString() + ", but was " + errLocs.get(i), errLocs.get(i), el.get(i).getAsString()); + } + + private OperationOutcomeIssueComponent findMatchingIssue(OperationOutcome oo, OperationOutcomeIssueComponent iss) { + for (OperationOutcomeIssueComponent t : oo.getIssue()) { + if (t.getExpression().get(0).getValue().equals(iss.getExpression().get(0).getValue()) && t.getCode() == iss.getCode() && t.getSeverity() == iss.getSeverity() + && (t.hasDiagnostics() ? t.getDiagnostics().equals(iss.getDiagnostics()) : !iss.hasDiagnostics()) && textMatches(t.getDetails().getText(), iss.getDetails().getText())) { + return t; } } - if (focus.has("output")) { - focus.remove("output"); + return null; + } + + private boolean textMatches(String t1, String t2) { + if (t1.endsWith("...")) { + t2 = t2.substring(0, t1.length()-3); + t1 = t1.substring(0, t1.length()-3); } - JsonArray vr = new JsonArray(); - java.add("output", vr); - for (ValidationMessage vm : errors) { - vr.add(vm.getDisplay()); + if (t2.endsWith("...")) { + t1 = t1.substring(0, t2.length()-3); + t2 = t2.substring(0, t2.length()-3); } + t1 = t1.trim(); + t2 = t2.trim(); + return t1.equals(t2); } private org.hl7.fhir.r4.model.Parameters makeExpProfile() { diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache index 6871f0af8..b1c215fa8 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/snomed.cache @@ -1843,3 +1843,24 @@ v: { "error" : "Unable to find code 2 in http://snomed.info/sct (version http://snomed.info/sct/900000000000207008/version/20220731); The code \"2\" is not valid in the system http://snomed.info/sct; The code provided (http://snomed.info/sct#2) is not valid in the value set 'All codes known to the system' (from http://tx.fhir.org/r4)" } ------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "56248011000036107", + "display" : "Panadol 500 mg tablet, 50" +}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}#### +v: { + "severity" : "error", + "error" : "Unable to find code 56248011000036107 in http://snomed.info/sct (version http://snomed.info/sct/900000000000207008/version/20220731); The code \"56248011000036107\" is not valid in the system http://snomed.info/sct; The code provided (http://snomed.info/sct#56248011000036107) is not valid in the value set 'All codes known to the system' (from http://tx.fhir.org/r4)" +} +------------------------------------------------------------------------------------- +{"code" : { + "system" : "http://snomed.info/sct", + "code" : "361055000", + "display" : "Misuses drugs (finding)" +}, "valueSet" :null, "lang":"null", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "versionFlexible":"false"}#### +v: { + "display" : "Misuses drugs", + "code" : "361055000", + "system" : "http://snomed.info/sct" +} +------------------------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index e16ced34f..1e323355b 100644 --- a/pom.xml +++ b/pom.xml @@ -14,12 +14,12 @@ HAPI FHIR --> org.hl7.fhir.core - 5.6.68-SNAPSHOT + 5.6.69-SNAPSHOT pom 5.4.0 - 1.1.113 + 1.1.114-SNAPSHOT 5.7.1 1.8.2 3.0.0-M5