Merge pull request #338 from hapifhir/gg_v518c

Preparing for new release
This commit is contained in:
Grahame Grieve 2020-09-08 13:44:33 +10:00 committed by GitHub
commit 03fb727894
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 347 additions and 93 deletions

View File

@ -3742,7 +3742,7 @@ public class VersionConvertor_40_50 {
return convertUsageContext((org.hl7.fhir.r4.model.UsageContext) src);
if (src instanceof org.hl7.fhir.r4.model.ElementDefinition)
return convertElementDefinition((org.hl7.fhir.r4.model.ElementDefinition) src);
throw new Error("Unknown type " + src.fhirType());
throw new FHIRException("Unknown type " + src.fhirType());
}
public static org.hl7.fhir.r4.model.Type convertType(org.hl7.fhir.r5.model.DataType src) throws FHIRException {
@ -3868,7 +3868,7 @@ public class VersionConvertor_40_50 {
return convertUsageContext((org.hl7.fhir.r5.model.UsageContext) src);
if (src instanceof org.hl7.fhir.r5.model.ElementDefinition)
return convertElementDefinition((org.hl7.fhir.r5.model.ElementDefinition) src);
throw new Error("Unknown type " + src.fhirType());
throw new FHIRException("Unknown type " + src.fhirType());
}
protected static void copyDomainResource(org.hl7.fhir.r4.model.DomainResource src, org.hl7.fhir.r5.model.DomainResource tgt) throws FHIRException {
@ -4174,7 +4174,7 @@ public class VersionConvertor_40_50 {
return VerificationResult40_50.convertVerificationResult((org.hl7.fhir.r4.model.VerificationResult) src);
if (src instanceof org.hl7.fhir.r4.model.VisionPrescription)
return VisionPrescription40_50.convertVisionPrescription((org.hl7.fhir.r4.model.VisionPrescription) src);
throw new Error("Unknown resource " + src.fhirType());
throw new FHIRException("Unknown resource " + src.fhirType());
}
public static org.hl7.fhir.r4.model.Resource convertResource(org.hl7.fhir.r5.model.Resource src) throws FHIRException {
@ -4441,7 +4441,7 @@ public class VersionConvertor_40_50 {
return VerificationResult40_50.convertVerificationResult((org.hl7.fhir.r5.model.VerificationResult) src);
if (src instanceof org.hl7.fhir.r5.model.VisionPrescription)
return VisionPrescription40_50.convertVisionPrescription((org.hl7.fhir.r5.model.VisionPrescription) src);
throw new Error("Unknown resource " + src.fhirType());
throw new FHIRException("Unknown resource " + src.fhirType());
}
protected static org.hl7.fhir.r5.model.CodeType convertResourceEnum(org.hl7.fhir.r4.model.CodeType src) {

View File

@ -295,8 +295,7 @@ public class BundleRenderer extends ResourceRenderer {
try {
xn = rr.build(be.getResource());
} catch (Exception e) {
xn = new XhtmlNode();
xn.para().b().tx("Exception generating narrative: "+e.getMessage());
xn = makeExceptionXhtml(e, "generating narrative");
}
}
root.blockquote().getChildNodes().addAll(checkInternalLinks(b, xn.getChildNodes()));

View File

@ -58,6 +58,7 @@ import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.Utilities;
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.utilities.xhtml.XhtmlParser;
@ -1021,6 +1022,21 @@ public class DataRenderer extends Renderer {
}
public XhtmlNode makeExceptionXhtml(Exception e, String function) {
XhtmlNode xn;
xn = new XhtmlNode(NodeType.Element, "div");
xn.para().b().tx("Exception "+function+": "+e.getMessage()).addComment(getStackTrace(e));
return xn;
}
private String getStackTrace(Exception e) {
StringBuilder b = new StringBuilder();
b.append("\r\n");
for (StackTraceElement t : e.getStackTrace()) {
b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber());
b.append("\r\n");
}
return b.toString();
}
}

View File

@ -41,6 +41,7 @@ import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.Narrative;
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.Period;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Property;
@ -320,6 +321,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
} else {
x.addText("??");
}
} else if (e instanceof org.hl7.fhir.r5.model.Integer64Type) {
if (((org.hl7.fhir.r5.model.Integer64Type) e).hasValue()) {
x.addText(Long.toString(((org.hl7.fhir.r5.model.Integer64Type) e).getValue()));
} else {
x.addText("??");
}
} else if (e instanceof org.hl7.fhir.r5.model.DecimalType) {
x.addText(((org.hl7.fhir.r5.model.DecimalType) e).getValue().toString());
} else if (e instanceof HumanName) {
@ -387,12 +394,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
} else if (e instanceof ElementDefinition) {
x.tx("todo-bundle");
} else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) {
StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(e.fhirType());
if (sd == null)
throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet, and no structure found");
else
generateByProfile(res, sd, ew, sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep(),
getChildrenForPath(sd.getSnapshot().getElement(), sd.getSnapshot().getElementFirstRep().getPath()), x, e.fhirType(), showCodeDetails, indent + 1);
throw new NotImplementedException("type "+e.getClass().getName()+" not handled - should not be here");
}
}
@ -553,12 +555,28 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
private boolean isPrimitive(ElementDefinition e) {
//we can tell if e is a primitive because it has types
if (e.getType().isEmpty())
if (e.getType().isEmpty()) {
return false;
if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode()))
}
if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) {
return false;
return true;
// return !e.getType().isEmpty()
}
if (e.getType().size() > 1) {
return true;
}
StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode());
if (sd != null) {
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
return true;
}
if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData",
"Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) {
return true;
}
}
}
return false;
}
private boolean isBase(String code) {
@ -646,31 +664,32 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
filterGrandChildren(grandChildren, path+"."+p.getName(), p);
if (p.getValues().size() > 0) {
if (isPrimitive(child)) {
XhtmlNode para = x.para();
String name = p.getName();
if (name.endsWith("[x]"))
name = name.substring(0, name.length() - 3);
if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) {
para.b().addText(name);
para.tx(": ");
if (renderAsList(child) && p.getValues().size() > 1) {
XhtmlNode list = x.ul();
for (BaseWrapper v : p.getValues())
renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent);
} else {
boolean first = true;
for (BaseWrapper v : p.getValues()) {
if (first)
first = false;
else
para.tx(", ");
renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent);
}
}
}
} else if (canDoTable(path, p, grandChildren)) {
XhtmlNode para = x.isPara() ? para = x : x.para();
String name = p.getName();
if (name.endsWith("[x]"))
name = name.substring(0, name.length() - 3);
if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) {
para.b().addText(name);
para.tx(": ");
if (renderAsList(child) && p.getValues().size() > 1) {
XhtmlNode list = x.ul();
for (BaseWrapper v : p.getValues())
renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent);
} else {
boolean first = true;
for (BaseWrapper v : p.getValues()) {
if (first) {
first = false;
} else {
para.tx(", ");
}
renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent);
}
}
}
} else if (canDoTable(path, p, grandChildren, x)) {
x.addTag(getHeader()).addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName()))));
XhtmlNode tbl = x.table( "grid");
XhtmlNode tbl = x.table("grid");
XhtmlNode tr = tbl.tr();
tr.td().tx("-"); // work around problem with empty table rows
addColumnHeadings(tr, grandChildren);
@ -737,10 +756,13 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
return res;
}
private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren) {
private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren, XhtmlNode x) {
if (isExtension(p)) {
return false;
}
if (x.getName().equals("p")) {
return false;
}
for (ElementDefinition e : grandChildren) {
List<PropertyWrapper> values = getValues(path, p, e);
if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e))
@ -830,15 +852,17 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
getContext().getWorker().cacheResource(ed);
}
}
if (p.getName().equals("modifierExtension") && ed == null)
if (p.getName().equals("modifierExtension") && ed == null) {
throw new DefinitionException("Unknown modifier extension "+url);
}
PropertyWrapper pe = map.get(p.getName()+"["+url+"]");
if (pe == null) {
if (ed == null) {
if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us"))
if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) {
throw new DefinitionException("unknown extension "+url);
}
// System.out.println("unknown extension "+url);
pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), ed.getSnapshot().getElementFirstRep());
pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null);
} else {
ElementDefinition def = ed.getSnapshot().getElement().get(0);
pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep());

View File

@ -795,6 +795,10 @@ public class FHIRPathEngine {
return item.primitiveValue();
} else if (item instanceof Quantity) {
Quantity q = (Quantity) item;
if (q.hasUnit() && Utilities.existsInList(q.getUnit(), "year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")
&& (!q.hasSystem() || q.getSystem().equals("http://unitsofmeasure.org"))) {
return q.getValue().toPlainString()+" "+q.getUnit();
}
if (q.getSystem().equals("http://unitsofmeasure.org")) {
String u = "'"+q.getCode()+"'";
return q.getValue().toPlainString()+" "+u;
@ -954,12 +958,14 @@ public class FHIRPathEngine {
if (!isString && !lexer.done() && (result.getConstant() instanceof IntegerType || result.getConstant() instanceof DecimalType) && (lexer.isStringConstant() || lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds"))) {
// it's a quantity
String ucum = null;
String unit = null;
if (lexer.hasToken("year", "years", "month", "months", "week", "weeks", "day", "days", "hour", "hours", "minute", "minutes", "second", "seconds", "millisecond", "milliseconds")) {
String s = lexer.take();
unit = s;
if (s.equals("year") || s.equals("years")) {
ucum = "a";
// this is not the UCUM year
} else if (s.equals("month") || s.equals("months")) {
ucum = "mo";
// this is not the UCUM month
} else if (s.equals("week") || s.equals("weeks")) {
ucum = "wk";
} else if (s.equals("day") || s.equals("days")) {
@ -976,7 +982,7 @@ public class FHIRPathEngine {
} else {
ucum = lexer.readConstant("units");
}
result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setSystem("http://unitsofmeasure.org").setCode(ucum));
result.setConstant(new Quantity().setValue(new BigDecimal(result.getConstant().primitiveValue())).setUnit(unit).setSystem(ucum == null ? null : "http://unitsofmeasure.org").setCode(ucum));
}
result.setEnd(lexer.getCurrentLocation());
} else if ("(".equals(lexer.getCurrent())) {
@ -1934,18 +1940,50 @@ public class FHIRPathEngine {
}
}
private boolean qtyEqual(Quantity left, Quantity right) {
private Boolean qtyEqual(Quantity left, Quantity right) {
if (!left.hasValue() && !right.hasValue()) {
return true;
}
if (!left.hasValue() || !right.hasValue()) {
return null;
}
if (worker.getUcumService() != null) {
DecimalType dl = qtyToCanonical(left);
DecimalType dr = qtyToCanonical(right);
Pair dl = qtyToCanonicalPair(left);
Pair dr = qtyToCanonicalPair(right);
if (dl != null && dr != null) {
return doEquals(dl, dr);
if (dl.getCode().equals(dr.getCode())) {
return doEquals(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));
} else {
return false;
}
}
}
return left.equals(right);
if (left.hasCode() || right.hasCode()) {
if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
return null;
}
} else if (!left.hasUnit() || right.hasUnit()) {
if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
return null;
}
}
return doEquals(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
}
private DecimalType qtyToCanonical(Quantity q) {
private Pair qtyToCanonicalPair(Quantity q) {
if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
return null;
}
try {
Pair p = new Pair(new Decimal(q.getValue().toPlainString()), q.getCode() == null ? "1" : q.getCode());
Pair c = worker.getUcumService().getCanonicalForm(p);
return c;
} catch (UcumException e) {
return null;
}
}
private DecimalType qtyToCanonicalDecimal(Quantity q) {
if (!"http://unitsofmeasure.org".equals(q.getSystem())) {
return null;
}
@ -1975,15 +2013,34 @@ public class FHIRPathEngine {
}
private boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
private Boolean qtyEquivalent(Quantity left, Quantity right) throws PathEngineException {
if (!left.hasValue() && !right.hasValue()) {
return true;
}
if (!left.hasValue() || !right.hasValue()) {
return null;
}
if (worker.getUcumService() != null) {
DecimalType dl = qtyToCanonical(left);
DecimalType dr = qtyToCanonical(right);
Pair dl = qtyToCanonicalPair(left);
Pair dr = qtyToCanonicalPair(right);
if (dl != null && dr != null) {
return doEquivalent(dl, dr);
if (dl.getCode().equals(dr.getCode())) {
return doEquivalent(new DecimalType(dl.getValue().asDecimal()), new DecimalType(dr.getValue().asDecimal()));
} else {
return false;
}
}
}
return left.equals(right);
if (left.hasCode() || right.hasCode()) {
if (!(left.hasCode() && right.hasCode()) || !left.getCode().equals(right.getCode())) {
return null;
}
} else if (!left.hasUnit() || right.hasUnit()) {
if (!(left.hasUnit() && right.hasUnit()) || !left.getUnit().equals(right.getUnit())) {
return null;
}
}
return doEquivalent(new DecimalType(left.getValue()), new DecimalType(right.getValue()));
}
@ -2072,9 +2129,9 @@ public class FHIRPathEngine {
return makeBoolean(false);
} else {
List<Base> dl = new ArrayList<Base>();
dl.add(qtyToCanonical((Quantity) left.get(0)));
dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
List<Base> dr = new ArrayList<Base>();
dr.add(qtyToCanonical((Quantity) right.get(0)));
dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
return opLessThan(dl, dr);
}
}
@ -2119,9 +2176,9 @@ public class FHIRPathEngine {
return makeBoolean(false);
} else {
List<Base> dl = new ArrayList<Base>();
dl.add(qtyToCanonical((Quantity) left.get(0)));
dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
List<Base> dr = new ArrayList<Base>();
dr.add(qtyToCanonical((Quantity) right.get(0)));
dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
return opGreater(dl, dr);
}
}
@ -2169,9 +2226,9 @@ public class FHIRPathEngine {
return makeBoolean(false);
} else {
List<Base> dl = new ArrayList<Base>();
dl.add(qtyToCanonical((Quantity) left.get(0)));
dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
List<Base> dr = new ArrayList<Base>();
dr.add(qtyToCanonical((Quantity) right.get(0)));
dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
return opLessOrEqual(dl, dr);
}
}
@ -2217,9 +2274,9 @@ public class FHIRPathEngine {
return makeBoolean(false);
} else {
List<Base> dl = new ArrayList<Base>();
dl.add(qtyToCanonical((Quantity) left.get(0)));
dl.add(qtyToCanonicalDecimal((Quantity) left.get(0)));
List<Base> dr = new ArrayList<Base>();
dr.add(qtyToCanonical((Quantity) right.get(0)));
dr.add(qtyToCanonicalDecimal((Quantity) right.get(0)));
return opGreaterOrEqual(dl, dr);
}
}
@ -4857,7 +4914,7 @@ public class FHIRPathEngine {
if (s.equals("year") || s.equals("years")) {
return Quantity.fromUcum(v, "a");
} else if (s.equals("month") || s.equals("months")) {
return Quantity.fromUcum(v, "mo");
return Quantity.fromUcum(v, "mo_s");
} else if (s.equals("week") || s.equals("weeks")) {
return Quantity.fromUcum(v, "wk");
} else if (s.equals("day") || s.equals("days")) {

View File

@ -145,7 +145,7 @@ public class FHIRPathTests {
public void test(String name, Element test) throws FileNotFoundException, IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException, UcumException {
// Setting timezone for this test. Grahame is in UTC+11, Travis is in GMT, and I'm here in Toronto, Canada with
// all my time based tests failing locally...
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
TimeZone.setDefault(TimeZone.getTimeZone("UTC+1100"));
fp.setHostServices(new FHIRPathTestEvaluationServices());
String input = test.getAttribute("inputfile");
@ -178,6 +178,7 @@ public class FHIRPathTests {
outcome.clear();
outcome.add(new BooleanType(ok));
}
System.out.println(name);
if (fp.hasLog()) {
System.out.println(name);
System.out.println(fp.takeLog());

View File

@ -152,7 +152,7 @@ public class NarrativeGenerationTests {
x = RendererFactory.factory(source, rc).render(new ElementWrappers.ResourceWrapperMetaElement(rc, e));
target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html"));
output = HEADER+new XhtmlComposer(true).compose(x)+FOOTER;
output = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER;
TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + "-meta.output.html"));
Assertions.assertTrue(output.equals(target), "Output does not match expected (meta)");
}

View File

@ -56,6 +56,12 @@ public class MarkDownProcessor {
public String process(String source, String context) {
if (source == null) {
return null;
}
if ("".equals(source)) {
return "";
}
switch (dialect) {
case DARING_FIREBALL : return Processor.process(source);
case COMMON_MARK : return processCommonMark(source);

View File

@ -161,7 +161,7 @@ public class I18nConstants {
public static final String FHIRPATH_UNKNOWN_CONSTANT = "FHIRPATH_UNKNOWN_CONSTANT";
public static final String FHIRPATH_UNKNOWN_CONTEXT = "FHIRPATH_UNKNOWN_CONTEXT";
public static final String FHIRPATH_UNKNOWN_CONTEXT_ELEMENT = "FHIRPATH_UNKNOWN_CONTEXT_ELEMENT";
public static final String FHIRPATH_UNKNOWN_NAME = "FHIRPATH_UNWKNOWN_NAME";
public static final String FHIRPATH_UNKNOWN_NAME = "FHIRPATH_UNKNOWN_NAME";
public static final String FHIRPATH_WRONG_PARAM_TYPE = "FHIRPATH_WRONG_PARAM_TYPE";
public static final String FIXED_TYPE_CHECKS_DT_ADDRESS_LINE = "Fixed_Type_Checks_DT_Address_Line";
public static final String FIXED_TYPE_CHECKS_DT_NAME_FAMILY = "Fixed_Type_Checks_DT_Name_Family";
@ -466,6 +466,7 @@ public class I18nConstants {
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX = "Type_Specific_Checks_DT_Primitive_Regex";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT = "Type_Specific_Checks_DT_Primitive_ValueExt";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS = "Type_Specific_Checks_DT_Primitive_WS";
public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = "TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS";
public static final String TYPE_SPECIFIC_CHECKS_DT_STRING_LENGTH = "Type_Specific_Checks_DT_String_Length";
public static final String TYPE_SPECIFIC_CHECKS_DT_STRING_WS = "Type_Specific_Checks_DT_String_WS";
public static final String TYPE_SPECIFIC_CHECKS_DT_TIME_VALID = "Type_Specific_Checks_DT_Time_Valid";
@ -578,6 +579,7 @@ public class I18nConstants {
public static final String XHTML_XHTML_ATTRIBUTE_ILLEGAL = "XHTML_XHTML_Attribute_Illegal";
public static final String XHTML_XHTML_DOCTYPE_ILLEGAL = "XHTML_XHTML_DOCTYPE_ILLEGAL";
public static final String XHTML_XHTML_ELEMENT_ILLEGAL = "XHTML_XHTML_Element_Illegal";
public static final String XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA = "XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA";
public static final String XHTML_XHTML_NAME_INVALID = "XHTML_XHTML_Name_Invalid";
public static final String XHTML_XHTML_NS_INVALID = "XHTML_XHTML_NS_InValid";
public static final String XML_ATTR_VALUE_INVALID = "xml_attr_value_invalid";

View File

@ -41,7 +41,10 @@ import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.primitive.XhtmlDt;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -137,11 +140,53 @@ public class XhtmlNode implements IBaseXhtml {
return this;
}
public void validate(List<String> errors, String path, boolean inResource, boolean inPara) {
if (nodeType == NodeType.Element || nodeType == NodeType.Document) {
path = Utilities.noString(path) ? name : path+"/"+name;
if (inResource) {
if (!Utilities.existsInList(name, "p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
"small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
"ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
"code", "samp", "img", "map", "area")) {
errors.add("Error at "+path+": Found "+name+" in a resource");
}
for (String an : attributes.keySet()) {
boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
"title", "style", "class", "ID", "lang", "xml:lang", "dir", "accesskey", "tabindex",
// tables
"span", "width", "align", "valign", "char", "charoff", "abbr", "axis", "headers", "scope", "rowspan", "colspan") ||
Utilities.existsInList(name + "." + an, "a.href", "a.name", "img.src", "img.border", "div.xmlns", "blockquote.cite", "q.cite",
"a.charset", "a.type", "a.name", "a.href", "a.hreflang", "a.rel", "a.rev", "a.shape", "a.coords", "img.src",
"img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
"area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
"table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
);
if (!ok)
errors.add("Error at "+path+": Found attribute "+name+"."+an+" in a resource");
}
}
if (inPara && Utilities.existsInList(name, "div", "blockquote", "table", "ol", "ul", "p")) {
errors.add("Error at "+path+": Found "+name+" inside an html paragraph");
}
if (childNodes != null) {
if ("p".equals(name)) {
inPara = true;
}
for (XhtmlNode child : childNodes) {
child.validate(errors, path, inResource, inPara);
}
}
}
}
public XhtmlNode addTag(String name)
{
if (!(nodeType == NodeType.Element || nodeType == NodeType.Document))
if (!(nodeType == NodeType.Element || nodeType == NodeType.Document)) {
throw new Error("Wrong node type - node is "+nodeType.toString()+" ('"+getName()+"/"+getContent()+"')");
}
XhtmlNode node = new XhtmlNode(NodeType.Element);
node.setName(name);
childNodes.add(node);
@ -188,7 +233,6 @@ public class XhtmlNode implements IBaseXhtml {
childNodes.add(node);
return node;
}
public XhtmlNode addText(String content)
{
if (!(nodeType == NodeType.Element || nodeType == NodeType.Document))
@ -747,6 +791,11 @@ public class XhtmlNode implements IBaseXhtml {
}
public boolean isPara() {
return "p".equals(name);
}

View File

@ -605,3 +605,5 @@ FHIRPATH_NUMERICAL_ONLY = Error evaluating FHIRPath expression: The function {0}
FHIRPATH_DECIMAL_ONLY = Error evaluating FHIRPath expression: The function {0} can only be used on a decimal but found {1}
FHIRPATH_FOCUS_PLURAL = Error evaluating FHIRPath expression: focus for {0} has more than one value
REFERENCE_REF_SUSPICIOUS = The syntax of the reference ''{0}'' looks incorrect, and it should be checked
TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = UCUM Codes that contain human readable annotations like {0} can be misleading. Best Practice is not to use annotations in the UCUM code, and rather to make sure that Quantity.unit is correctly human readable
XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA = Illegal element name inside in a paragraph in the XHTML (''{0}'')

View File

@ -2,7 +2,12 @@ package org.hl7.fhir.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
/*
Copyright (c) 2011+, HL7, Inc.
@ -64,6 +69,8 @@ POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
@ -78,10 +85,28 @@ import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.instance.utils.IndexedElement;
public class BaseValidator {
public class ValidationControl {
private boolean allowed;
private IssueSeverity level;
public ValidationControl(boolean allowed, IssueSeverity level) {
super();
this.allowed = allowed;
this.level = level;
}
public boolean isAllowed() {
return allowed;
}
public IssueSeverity getLevel() {
return level;
}
}
protected final String META = "meta";
protected final String ENTRY = "entry";
protected final String DOCUMENT = "document";
@ -98,7 +123,16 @@ public class BaseValidator {
protected Source source;
protected IWorkerContext context;
protected TimeTracker timeTracker = new TimeTracker();
/**
* Use to control what validation the validator performs.
* Using this, you can turn particular kinds of validation on and off
* In addition, you can override the error | warning | hint level and make it a different level
*
* There is no way to do this using the command line validator; it's a service that is only
* offered when the validator is hosted in some other process
*/
private Map<String, ValidationControl> validationControl = new HashMap<>();
public BaseValidator(IWorkerContext context){
this.context = context;
@ -287,7 +321,10 @@ public class BaseValidator {
protected boolean txRule(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String message = context.formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setTxLink(txLink));
ValidationMessage vm = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, message, IssueSeverity.ERROR).setMessageId(theMessage);
if (checkMsgId(theMessage, vm)) {
errors.add(vm.setTxLink(txLink));
}
}
return thePass;
}
@ -415,10 +452,23 @@ public class BaseValidator {
protected ValidationMessage addValidationMessage(List<ValidationMessage> errors, IssueType type, int line, int col, String path, String msg, IssueSeverity theSeverity, Source theSource, String id) {
ValidationMessage validationMessage = new ValidationMessage(theSource, type, line, col, path, msg, theSeverity).setMessageId(id);
errors.add(validationMessage);
if (checkMsgId(id, validationMessage)) {
errors.add(validationMessage);
}
return validationMessage;
}
public boolean checkMsgId(String id, ValidationMessage vm) {
if (id != null && validationControl.containsKey(id)) {
ValidationControl control = validationControl.get(id);
if (control.level != null) {
vm.setLevel(control.level);
}
return control.isAllowed();
}
return true;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
@ -429,7 +479,10 @@ public class BaseValidator {
protected boolean txWarning(List<ValidationMessage> errors, String txLink, IssueType type, int line, int col, String path, boolean thePass, String msg, Object... theMessageArguments) {
if (!thePass) {
String nmsg = context.formatMessage(msg, theMessageArguments);
errors.add(new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg));
ValidationMessage vmsg = new ValidationMessage(Source.TerminologyEngine, type, line, col, path, nmsg, IssueSeverity.WARNING).setTxLink(txLink).setMessageId(msg);
if (checkMsgId(msg, vmsg)) {
errors.add(vmsg);
}
}
return thePass;
@ -567,7 +620,10 @@ public class BaseValidator {
}
protected void addValidationMessage(List<ValidationMessage> errors, IssueType type, String path, String msg, String html, IssueSeverity theSeverity, String id) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity).setMessageId(id));
ValidationMessage vm = new ValidationMessage(source, type, -1, -1, path, msg, html, theSeverity);
if (checkMsgId(id, vm)) {
errors.add(vm.setMessageId(id));
}
}
/**
@ -802,5 +858,9 @@ public class BaseValidator {
}
}
public Map<String, ValidationControl> getValidationControl() {
return validationControl;
}
}

View File

@ -34,6 +34,7 @@ import org.hl7.fhir.r5.utils.*;
import org.hl7.fhir.r5.utils.IResourceValidator.*;
import org.hl7.fhir.r5.utils.StructureMapUtilities.ITransformerServices;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.validation.BaseValidator.ValidationControl;
import org.hl7.fhir.validation.cli.services.StandAloneValidatorFetcher.IPackageInstaller;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.utilities.IniFile;
@ -309,6 +310,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
private List<ImplementationGuide> igs = new ArrayList<>();
private boolean showTimes;
private List<BundleValidationRule> bundleValidationRules = new ArrayList<>();
private Map<String, ValidationControl> validationControl = new HashMap<>();
private class AsteriskFilter implements FilenameFilter {
String dir;
@ -1580,6 +1582,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
validator.setFetcher(this);
validator.getImplementationGuides().addAll(igs);
validator.getBundleValidationRules().addAll(bundleValidationRules);
validator.getValidationControl().putAll(validationControl );
return validator;
}
@ -2391,9 +2394,27 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst
return pcm.packageExists(id, ver);
}
public void loadPackage(String id, String ver) throws IOException, FHIRException {
loadIg(id+(ver == null ? "" : "#"+ver), true);
}
/**
* Systems that host the ValidationEngine can use this to control what validation the validator performs.
*
* Using this, you can turn particular kinds of validation on and off. In addition, you can override
* the error | warning | hint level and make it a different level.
*
* Each entry has
* * 'allowed': a boolean flag. if this is false, the Validator will not report the error.
* * 'level' : set to error, warning, information
*
* Entries are registered by ID, using the IDs in /org.hl7.fhir.utilities/src/main/resources/Messages.properties
*
* This feature is not supported by the validator CLI - and won't be. It's for systems hosting
* the validation framework in their own implementation context
*/
public Map<String, ValidationControl> getValidationControl() {
return validationControl;
}
}

View File

@ -491,17 +491,17 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false;
}
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message) {
private void bpCheck(List<ValidationMessage> errors, IssueType invalid, int line, int col, String literalPath, boolean test, String message, Object... theMessageArguments) {
if (bpWarnings != null) {
switch (bpWarnings) {
case Error:
rule(errors, invalid, line, col, literalPath, test, message);
rule(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
case Warning:
warning(errors, invalid, line, col, literalPath, test, message);
warning(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
case Hint:
hint(errors, invalid, line, col, literalPath, test, message);
hint(errors, invalid, line, col, literalPath, test, message, theMessageArguments);
break;
default: // do nothing
break;
@ -2070,7 +2070,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
checkInnerNS(errors, e, path, xhtml.getChildNodes());
rule(errors, IssueType.INVALID, e.line(), e.col(), path, "div".equals(xhtml.getName()), I18nConstants.XHTML_XHTML_NAME_INVALID, ns);
// check that no illegal elements and attributes have been used
checkInnerNames(errors, e, path, xhtml.getChildNodes());
checkInnerNames(errors, e, path, xhtml.getChildNodes(), false);
checkUrls(errors, e, path, xhtml.getChildNodes());
}
}
@ -2171,7 +2171,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
"http://hl7.org/fhirpath/System.Decimal", "http://hl7.org/fhirpath/System.Date", "http://hl7.org/fhirpath/System.Time", "http://hl7.org/fhirpath/System.DateTime", "http://hl7.org/fhirpath/System.Quantity");
}
private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list) {
private void checkInnerNames(List<ValidationMessage> errors, Element e, String path, List<XhtmlNode> list, boolean inPara) {
for (XhtmlNode node : list) {
if (node.getNodeType() == NodeType.Comment) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !node.getContent().startsWith("DOCTYPE"), I18nConstants.XHTML_XHTML_DOCTYPE_ILLEGAL);
@ -2181,9 +2181,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
"p", "br", "div", "h1", "h2", "h3", "h4", "h5", "h6", "a", "span", "b", "em", "i", "strong",
"small", "big", "tt", "small", "dfn", "q", "var", "abbr", "acronym", "cite", "blockquote", "hr", "address", "bdo", "kbd", "q", "sub", "sup",
"ul", "ol", "li", "dl", "dt", "dd", "pre", "table", "caption", "colgroup", "col", "thead", "tr", "tfoot", "tbody", "th", "td",
"code", "samp", "img", "map", "area"
), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName());
"code", "samp", "img", "map", "area"), I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL, node.getName());
for (String an : node.getAttributes().keySet()) {
boolean ok = an.startsWith("xmlns") || Utilities.existsInList(an,
"title", "style", "class", ID, "lang", "xml:lang", "dir", "accesskey", "tabindex",
@ -2195,11 +2194,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
"img.alt", "img.longdesc", "img.height", "img.width", "img.usemap", "img.ismap", "map.name", "area.shape",
"area.coords", "area.href", "area.nohref", "area.alt", "table.summary", "table.width", "table.border",
"table.frame", "table.rules", "table.cellspacing", "table.cellpadding", "pre.space", "td.nowrap"
);
if (!ok)
);
if (!ok) {
rule(errors, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.XHTML_XHTML_ATTRIBUTE_ILLEGAL, an, node.getName());
}
}
checkInnerNames(errors, e, path, node.getChildNodes());
rule(errors, IssueType.INVALID, e.line(), e.col(), path, !(inPara && Utilities.existsInList(node.getName(), "div", "blockquote", "table", "ol", "ul", "p")) , I18nConstants.XHTML_XHTML_ELEMENT_ILLEGAL_IN_PARA, node.getName());
checkInnerNames(errors, e, path, node.getChildNodes(), inPara || "p".equals(node.getName()));
}
}
}
@ -2320,6 +2323,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (system != null || code != null ) {
checkCodedElement(theErrors, thePath, element, theProfile, definition, false, false, theStack, code, system, unit);
}
if (code != null && "http://unitsofmeasure.org".equals(system)) {
int b = code.indexOf("{");
int e = code.indexOf("}");
if (b >= 0 && e > 0 && b < e) {
bpCheck(theErrors, IssueType.BUSINESSRULE, element.line(), element.col(), thePath, !code.contains("{"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS, code.substring(b, e+1));
}
}
}
private void checkAttachment(List<ValidationMessage> errors, String path, Element element, StructureDefinition theProfile, ElementDefinition definition, boolean theInCodeableConcept, boolean theCheckDisplayInContext, NodeStack theStack) {

View File

@ -36,6 +36,7 @@ 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.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule;
import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher;
import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy;
@ -204,6 +205,9 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
} else {
val.setDebug(false);
}
if (content.has("best-practice")) {
val.setBestPracticeWarningLevel(BestPracticeWarningLevel.valueOf(content.get("best-practice").getAsString()));
}
if (content.has("examples")) {
val.setAllowExamples(content.get("examples").getAsBoolean());
} else {
@ -367,10 +371,12 @@ public class ValidationTestSuite implements IEvaluationContext, IValidatorResour
}
if (!TestingUtilities.context(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"))
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"))
}
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");