Merge pull request #670 from hapifhir/gg-202111-xver-modifiers

Gg 202111 xver modifiers
This commit is contained in:
Grahame Grieve 2021-11-22 10:07:30 +11:00 committed by GitHub
commit 804c80b009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 728 additions and 45 deletions

View File

@ -4,7 +4,7 @@
| :---: |
| [![Build Status][Badge-BuildPipeline]][Link-BuildPipeline] |
This is the core object handling code, with utilities (including validator), for the FHIR specification.
This is the java core object handling code, with utilities (including validator), for the FHIR specification.
included in this repo:
* org.fhir.fhir.utilities: Shared code used by all the other projects - including the internationalization code
@ -17,6 +17,11 @@ included in this repo:
* org.fhir.fhir.validation: The FHIR Java validator (note: based on R5 internally, but validates all the above versions)
* org.fhir.fhir.validation.cli: Holder project for releasing the FHIR validator as as single fat jar (will be removed in the future)
This code is used in all HAPI servers and clients, and also is the HL7 maintained
FHIR Validator. In addition, this is the core code for the HL7 maintained IG publisher
and FHIR main build publisher. As such, this code is considered an authoritatively
correct implementation of the core FHIR specification that it implements.
### CI/CD
All integration and delivery done on Azure pipelines. Azure project can be viewed [here][Link-AzureProject].

View File

@ -0,0 +1,6 @@
Validator:
* fix processing of modifier extensions and cross-version modifier extensions
Other changes:
* improvements to data types rendering based on new test cases (URLs, Money, Markdown)
* add locale to rendering context, and fix up timezone related rendering based on locale

View File

@ -0,0 +1,387 @@
package org.hl7.fhir.convertors.misc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.naming.ldap.StartTlsRequest;
import javax.xml.parsers.ParserConfigurationException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.xml.sax.SAXException;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class XVerPackegeFixer {
private static final String R5_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r5.core\\package";
private static final String R4_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r4.core\\package";
private static final String R3_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r3.core\\package";
private static final String R2B_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r2b.core\\package";
private static final String R2_FOLDER = "C:\\work\\org.hl7.fhir\\packages\\hl7.fhir.rX\\hl7.fhir.r2.core\\package";
private static int mod;
private static Map<String, org.hl7.fhir.r5.model.StructureDefinition> map5 = new HashMap<>();
private static Map<String, org.hl7.fhir.r4.model.StructureDefinition> map4 = new HashMap<>();
private static Map<String, org.hl7.fhir.dstu3.model.StructureDefinition> map3 = new HashMap<>();
private static Map<String, org.hl7.fhir.dstu2.model.StructureDefinition> map2 = new HashMap<>();
private static Map<String, org.hl7.fhir.dstu2016may.model.StructureDefinition> map2b = new HashMap<>();
public static void main(String[] args) throws FileNotFoundException, ParserConfigurationException, SAXException, IOException {
mod = 0;
for (File f : new File(args[0]).listFiles()) {
if (f.getName().startsWith("xver-")) {
JsonObject j = JsonTrackingParser.parseJson(f);
fixUp(j, f.getName());
JsonTrackingParser.write(j, f, true);
}
}
System.out.println("all done: "+mod+" modifiers");
}
private static void fixUp(JsonObject j, String name) throws FHIRFormatError, FileNotFoundException, IOException {
name = name.replace(".json", "");
System.out.println("Process "+name);
String version = name.substring(name.lastIndexOf("-")+1);
int i = 0;
for (Entry<String, JsonElement> e : j.entrySet()) {
if (i == 50) {
i = 0;
System.out.print(".");
}
i++;
String n = e.getKey();
JsonObject o = ((JsonObject) e.getValue());
boolean ok = (o.has("types") && o.getAsJsonArray("types").size() > 0) || (o.has("elements") && o.getAsJsonArray("elements").size() > 0);
if (!ok) {
List<String> types = new ArrayList<>();
List<String> elements = new ArrayList<>();
getElementInfo(version, n, types, elements);
if (elements.size() > 0) {
JsonArray arr = o.getAsJsonArray("elements");
if (arr == null) {
arr = new JsonArray();
o.add("elements", arr);
}
for (String s : types) {
arr.add(s);
}
} else if (types.size() > 0) {
JsonArray arr = o.getAsJsonArray("types");
if (arr == null) {
arr = new JsonArray();
o.add("types", arr);
}
for (String s : types) {
arr.add(s);
}
}
}
}
System.out.println("done");
}
private static boolean getElementInfo(String version, String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
if ("contained".equals(n.substring(n.indexOf(".")+1))) {
return false;
}
switch (version) {
case "4.6": return getElementInfoR5(n, types, elements);
case "4.0": return getElementInfoR4(n, types, elements);
case "3.0": return getElementInfoR3(n, types, elements);
case "1.4": return getElementInfoR2B(n, types, elements);
case "1.0": return getElementInfoR2(n, types, elements);
}
return false;
}
private static Object tail(String value) {
return value.contains("/") ? value.substring(value.lastIndexOf("/")+1) : value;
}
private static boolean getElementInfoR5(String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
String tn = n.substring(0, n.indexOf("."));
org.hl7.fhir.r5.model.StructureDefinition sd = null;
if (map5.containsKey(tn)) {
sd = map5.get(tn);
} else {
sd = (org.hl7.fhir.r5.model.StructureDefinition) new org.hl7.fhir.r5.formats.JsonParser().parse(new FileInputStream(Utilities.path(R5_FOLDER, "StructureDefinition-"+tn+".json")));
map5.put(tn, sd);
}
for (org.hl7.fhir.r5.model.ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(n)) {
List<org.hl7.fhir.r5.model.ElementDefinition> children = listChildrenR5(sd.getSnapshot().getElement(), ed);
if (children.size() > 0) {
for (org.hl7.fhir.r5.model.ElementDefinition c : children) {
String en = c.getPath().substring(ed.getPath().length()+1);
elements.add(en);
}
} else {
for (org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent t : ed.getType()) {
if (t.hasTargetProfile()) {
StringBuilder b = new StringBuilder();
b.append(t.getWorkingCode());
b.append("(");
boolean first = true;
for (org.hl7.fhir.r5.model.CanonicalType u : t.getTargetProfile()) {
if (first) first = false; else b.append("|");
b.append(tail(u.getValue()));
}
b.append(")");
types.add(b.toString());
} else {
types.add(t.getWorkingCode());
}
}
}
}
}
return false;
}
private static List<org.hl7.fhir.r5.model.ElementDefinition> listChildrenR5(List<org.hl7.fhir.r5.model.ElementDefinition> list, org.hl7.fhir.r5.model.ElementDefinition ed) {
List<org.hl7.fhir.r5.model.ElementDefinition> res = new ArrayList<>();
for (org.hl7.fhir.r5.model.ElementDefinition t : list) {
String p = t.getPath();
if (p.startsWith(ed.getPath()+".")) {
p = p.substring(ed.getPath().length()+1);
if (!p.contains(".")) {
res.add(t);
}
}
}
return res;
}
private static boolean getElementInfoR4(String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
String tn = n.substring(0, n.indexOf("."));
org.hl7.fhir.r4.model.StructureDefinition sd = null;
if (map4.containsKey(tn)) {
sd = map4.get(tn);
} else {
sd = (org.hl7.fhir.r4.model.StructureDefinition) new org.hl7.fhir.r4.formats.JsonParser().parse(new FileInputStream(Utilities.path(R4_FOLDER, "StructureDefinition-"+tn+".json")));
map4.put(tn, sd);
}
for (org.hl7.fhir.r4.model.ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(n)) {
List<org.hl7.fhir.r4.model.ElementDefinition> children = listChildrenR4(sd.getSnapshot().getElement(), ed);
if (children.size() > 0) {
for (org.hl7.fhir.r4.model.ElementDefinition c : children) {
String en = c.getPath().substring(ed.getPath().length()+1);
elements.add(en);
}
} else {
for (org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent t : ed.getType()) {
if (t.hasTargetProfile()) {
StringBuilder b = new StringBuilder();
b.append(t.getWorkingCode());
b.append("(");
boolean first = true;
for (org.hl7.fhir.r4.model.CanonicalType u : t.getTargetProfile()) {
if (first) first = false; else b.append("|");
b.append(tail(u.getValue()));
}
b.append(")");
types.add(b.toString());
} else {
types.add(t.getWorkingCode());
}
}
}
}
}
return false;
}
private static List<org.hl7.fhir.r4.model.ElementDefinition> listChildrenR4(List<org.hl7.fhir.r4.model.ElementDefinition> list, org.hl7.fhir.r4.model.ElementDefinition ed) {
List<org.hl7.fhir.r4.model.ElementDefinition> res = new ArrayList<>();
for (org.hl7.fhir.r4.model.ElementDefinition t : list) {
String p = t.getPath();
if (p.startsWith(ed.getPath()+".")) {
p = p.substring(ed.getPath().length()+1);
if (!p.contains(".")) {
res.add(t);
}
}
}
return res;
}
private static boolean getElementInfoR3(String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
String tn = n.substring(0, n.indexOf("."));
org.hl7.fhir.dstu3.model.StructureDefinition sd = null;
if (map3.containsKey(tn)) {
sd = map3.get(tn);
} else {
sd = (org.hl7.fhir.dstu3.model.StructureDefinition) new org.hl7.fhir.dstu3.formats.JsonParser().parse(new FileInputStream(Utilities.path(R3_FOLDER, "StructureDefinition-"+tn+".json")));
map3.put(tn, sd);
}
for (org.hl7.fhir.dstu3.model.ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(n)) {
List<org.hl7.fhir.dstu3.model.ElementDefinition> children = listChildrenR3(sd.getSnapshot().getElement(), ed);
if (children.size() > 0) {
for (org.hl7.fhir.dstu3.model.ElementDefinition c : children) {
String en = c.getPath().substring(ed.getPath().length()+1);
elements.add(en);
}
} else {
for (org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent t : ed.getType()) {
if (t.hasTargetProfile()) {
StringBuilder b = new StringBuilder();
b.append(t.getCode());
b.append("(");
b.append(tail(t.getTargetProfile()));
b.append(")");
types.add(b.toString());
} else {
types.add(t.getCode());
}
}
}
}
}
return false;
}
private static List<org.hl7.fhir.dstu3.model.ElementDefinition> listChildrenR3(List<org.hl7.fhir.dstu3.model.ElementDefinition> list, org.hl7.fhir.dstu3.model.ElementDefinition ed) {
List<org.hl7.fhir.dstu3.model.ElementDefinition> res = new ArrayList<>();
for (org.hl7.fhir.dstu3.model.ElementDefinition t : list) {
String p = t.getPath();
if (p.startsWith(ed.getPath()+".")) {
p = p.substring(ed.getPath().length()+1);
if (!p.contains(".")) {
res.add(t);
}
}
}
return res;
}
private static boolean getElementInfoR2(String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
String tn = n.substring(0, n.indexOf("."));
org.hl7.fhir.dstu2.model.StructureDefinition sd = null;
if (map2.containsKey(tn)) {
sd = map2.get(tn);
} else {
sd = (org.hl7.fhir.dstu2.model.StructureDefinition) new org.hl7.fhir.dstu2.formats.JsonParser().parse(new FileInputStream(Utilities.path(R2_FOLDER, "StructureDefinition-"+tn+".json")));
map2.put(tn, sd);
}
for (org.hl7.fhir.dstu2.model.ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(n)) {
List<org.hl7.fhir.dstu2.model.ElementDefinition> children = listChildrenR2(sd.getSnapshot().getElement(), ed);
if (children.size() > 0) {
for (org.hl7.fhir.dstu2.model.ElementDefinition c : children) {
String en = c.getPath().substring(ed.getPath().length()+1);
elements.add(en);
}
} else {
for (org.hl7.fhir.dstu2.model.ElementDefinition.TypeRefComponent t : ed.getType()) {
if (t.hasProfile()) {
StringBuilder b = new StringBuilder();
b.append(t.getCode());
b.append("(");
boolean first = true;
for (org.hl7.fhir.dstu2.model.UriType u : t.getProfile()) {
if (first) first = false; else b.append("|");
b.append(tail(u.getValue()));
}
b.append(")");
types.add(b.toString());
} else {
types.add(t.getCode());
}
}
}
}
}
return false;
}
private static List<org.hl7.fhir.dstu2.model.ElementDefinition> listChildrenR2(List<org.hl7.fhir.dstu2.model.ElementDefinition> list, org.hl7.fhir.dstu2.model.ElementDefinition ed) {
List<org.hl7.fhir.dstu2.model.ElementDefinition> res = new ArrayList<>();
for (org.hl7.fhir.dstu2.model.ElementDefinition t : list) {
String p = t.getPath();
if (p.startsWith(ed.getPath()+".")) {
p = p.substring(ed.getPath().length()+1);
if (!p.contains(".")) {
res.add(t);
}
}
}
return res;
}
private static boolean getElementInfoR2B(String n, List<String> types, List<String> elements) throws FHIRFormatError, FileNotFoundException, IOException {
String tn = n.substring(0, n.indexOf("."));
org.hl7.fhir.dstu2016may.model.StructureDefinition sd = null;
if (map2b.containsKey(tn)) {
sd = map2b.get(tn);
} else {
sd = (org.hl7.fhir.dstu2016may.model.StructureDefinition) new org.hl7.fhir.dstu2016may.formats.JsonParser().parse(new FileInputStream(Utilities.path(R2B_FOLDER, "StructureDefinition-"+tn+".json")));
map2b.put(tn, sd);
}
for (org.hl7.fhir.dstu2016may.model.ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(n)) {
List<org.hl7.fhir.dstu2016may.model.ElementDefinition> children = listChildrenR2B(sd.getSnapshot().getElement(), ed);
if (children.size() > 0) {
for (org.hl7.fhir.dstu2016may.model.ElementDefinition c : children) {
String en = c.getPath().substring(ed.getPath().length()+1);
elements.add(en);
}
} else {
for (org.hl7.fhir.dstu2016may.model.ElementDefinition.TypeRefComponent t : ed.getType()) {
if (t.hasProfile()) {
StringBuilder b = new StringBuilder();
b.append(t.getCode());
b.append("(");
boolean first = true;
for (org.hl7.fhir.dstu2016may.model.UriType u : t.getProfile()) {
if (first) first = false; else b.append("|");
b.append(tail(u.getValue()));
}
b.append(")");
types.add(b.toString());
} else {
types.add(t.getCode());
}
}
}
}
}
return false;
}
private static List<org.hl7.fhir.dstu2016may.model.ElementDefinition> listChildrenR2B(List<org.hl7.fhir.dstu2016may.model.ElementDefinition> list, org.hl7.fhir.dstu2016may.model.ElementDefinition ed) {
List<org.hl7.fhir.dstu2016may.model.ElementDefinition> res = new ArrayList<>();
for (org.hl7.fhir.dstu2016may.model.ElementDefinition t : list) {
String p = t.getPath();
if (p.startsWith(ed.getPath()+".")) {
p = p.substring(ed.getPath().length()+1);
if (!p.contains(".")) {
res.add(t);
}
}
}
return res;
}
}

View File

@ -2,7 +2,18 @@ package org.hl7.fhir.r5.renderers;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Currency;
import java.util.List;
import java.util.TimeZone;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
@ -33,8 +44,10 @@ import org.hl7.fhir.r5.model.Expression;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.HumanName;
import org.hl7.fhir.r5.model.HumanName.NameUse;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.MarkdownType;
import org.hl7.fhir.r5.model.Money;
import org.hl7.fhir.r5.model.Period;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Quantity;
@ -64,6 +77,9 @@ 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;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
@ -373,6 +389,8 @@ public class DataRenderer extends Renderer {
return displayTiming((Timing) type);
} else if (type instanceof SampledData) {
return displaySampledData((SampledData) type);
} else if (type.isDateTime()) {
return displayDateTime((BaseDateTimeType) type);
} else if (type.isPrimitive()) {
return type.primitiveValue();
} else {
@ -380,11 +398,58 @@ public class DataRenderer extends Renderer {
}
}
private String displayDateTime(BaseDateTimeType type) {
if (!type.hasPrimitiveValue()) {
return "";
}
// relevant inputs in rendering context:
// timeZone, dateTimeFormat, locale, mode
// timezone - application specified timezone to use.
// null = default to the time of the date/time itself
// dateTimeFormat - application specified format for date times
// null = default to ... depends on mode
// mode - if rendering mode is technical, format defaults to XML format
// locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale)
if (isOnlyDate(type.getPrecision())) {
DateTimeFormatter fmt = context.getDateFormat();
if (fmt == null) {
if (context.isTechnicalMode()) {
fmt = DateTimeFormatter.ISO_DATE;
} else {
fmt = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale());
}
}
LocalDate date = LocalDate.of(type.getYear(), type.getMonth()+1, type.getDay());
return fmt.format(date);
}
DateTimeFormatter fmt = context.getDateTimeFormat();
if (fmt == null) {
if (context.isTechnicalMode()) {
fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
} else {
fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale());
}
}
ZonedDateTime zdt = ZonedDateTime.parse(type.primitiveValue());
ZoneId zone = context.getTimeZoneId();
if (zone != null) {
zdt = zdt.withZoneSameInstant(zone);
}
return fmt.format(zdt);
}
private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) {
return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY;
}
public String display(BaseWrapper type) {
return "to do";
}
public void render(XhtmlNode x, BaseWrapper type) {
public void render(XhtmlNode x, BaseWrapper type) throws FHIRFormatError, DefinitionException, IOException {
Base base = null;
try {
base = type.getBase();
@ -399,7 +464,7 @@ public class DataRenderer extends Renderer {
}
}
public void renderBase(XhtmlNode x, Base b) {
public void renderBase(XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException {
if (b instanceof DataType) {
render(x, (DataType) b);
} else {
@ -407,9 +472,9 @@ public class DataRenderer extends Renderer {
}
}
public void render(XhtmlNode x, DataType type) {
if (type instanceof DateTimeType) {
renderDateTime(x, (DateTimeType) type);
public void render(XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException {
if (type instanceof BaseDateTimeType) {
x.tx(displayDateTime((BaseDateTimeType) type));
} else if (type instanceof UriType) {
renderUri(x, (UriType) type);
} else if (type instanceof Annotation) {
@ -424,6 +489,10 @@ public class DataRenderer extends Renderer {
renderHumanName(x, (HumanName) type);
} else if (type instanceof Address) {
renderAddress(x, (Address) type);
} else if (type instanceof Expression) {
renderExpression(x, (Expression) type);
} else if (type instanceof Money) {
renderMoney(x, (Money) type);
} else if (type instanceof ContactPoint) {
renderContactPoint(x, (ContactPoint) type);
} else if (type instanceof Quantity) {
@ -438,10 +507,8 @@ public class DataRenderer extends Renderer {
renderSampledData(x, (SampledData) type);
} else if (type instanceof Reference) {
renderReference(x, (Reference) type);
} else if (type instanceof InstantType) {
x.tx(((InstantType) type).toHumanDisplay());
} else if (type instanceof BaseDateTimeType) {
x.tx(((BaseDateTimeType) type).toHumanDisplay());
} else if (type instanceof MarkdownType) {
addMarkdown(x, ((MarkdownType) type).asStringValue());
} else if (type.isPrimitive()) {
x.tx(type.primitiveValue());
} else {
@ -461,22 +528,24 @@ public class DataRenderer extends Renderer {
public void renderDateTime(XhtmlNode x, Base e) {
if (e.hasPrimitiveValue()) {
x.addText(((DateTimeType) e).toHumanDisplay());
x.addText(displayDateTime((DateTimeType) e));
}
}
public void renderDateTime(XhtmlNode x, String s) {
if (s != null) {
DateTimeType dt = new DateTimeType(s);
x.addText(dt.toHumanDisplay());
x.addText(displayDateTime(dt));
}
}
protected void renderUri(XhtmlNode x, UriType uri) {
if (uri.getValue().startsWith("mailto:")) {
x.ah(uri.getValue()).addText(uri.getValue().substring(7));
} else {
} else if (Utilities.isAbsoluteUrlLinkable(uri.getValue()) && !(uri instanceof IdType)) {
x.ah(uri.getValue()).addText(uri.getValue());
} else {
x.addText(uri.getValue());
}
}
@ -904,6 +973,27 @@ public class DataRenderer extends Renderer {
return s.toString();
}
protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) {
NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale());
numberFormat.setGroupingUsed(true);
numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits());
numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits());
return numberFormat.format(input);
}
protected void renderMoney(XhtmlNode x, Money money) {
Currency c = Currency.getInstance(money.getCurrency());
if (c != null) {
XhtmlNode s = x.span(null, c.getDisplayName());
s.tx(c.getSymbol(context.getLocale()));
s.tx(getLocalizedBigDecimalValue(money.getValue(), c));
x.tx(" ("+c.getCurrencyCode()+")");
} else {
x.tx(money.getCurrency());
x.tx(money.getValue().toPlainString());
}
}
protected void renderExpression(XhtmlNode x, Expression expr) {
// there's two parts: what the expression is, and how it's described.
// we start with what it is, and then how it's desceibed
@ -1077,19 +1167,19 @@ public class DataRenderer extends Renderer {
x.tx(" "+q.getLow().getUnit());
}
public static String displayPeriod(Period p) {
String s = !p.hasStart() ? "(?)" : p.getStartElement().toHumanDisplay();
public String displayPeriod(Period p) {
String s = !p.hasStart() ? "(?)" : displayDateTime(p.getStartElement());
s = s + " --> ";
return s + (!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
return s + (!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement()));
}
public void renderPeriod(XhtmlNode x, Period p) {
x.addText(!p.hasStart() ? "??" : p.getStartElement().toHumanDisplay());
x.addText(!p.hasStart() ? "??" : displayDateTime(p.getStartElement()));
x.tx(" --> ");
x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
x.addText(!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement()));
}
public void renderDataRequirement(XhtmlNode x, DataRequirement dr) {
public void renderDataRequirement(XhtmlNode x, DataRequirement dr) throws FHIRFormatError, DefinitionException, IOException {
XhtmlNode tbl = x.table("grid");
XhtmlNode tr = tbl.tr();
XhtmlNode td = tr.td().colspan("2");
@ -1194,7 +1284,7 @@ public class DataRenderer extends Renderer {
CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
for (DateTimeType p : s.getEvent()) {
if (p.hasValue()) {
c.append(p.toHumanDisplay());
c.append(displayDateTime(p));
} else if (!renderExpression(c, p)) {
c.append("??");
}
@ -1205,7 +1295,7 @@ public class DataRenderer extends Renderer {
if (s.hasRepeat()) {
TimingRepeatComponent rep = s.getRepeat();
if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart())
b.append("Starting "+rep.getBoundsPeriod().getStartElement().toHumanDisplay());
b.append("Starting "+displayDateTime(rep.getBoundsPeriod().getStartElement()));
if (rep.hasCount())
b.append("Count "+Integer.toString(rep.getCount())+" times");
if (rep.hasDuration())
@ -1237,7 +1327,7 @@ public class DataRenderer extends Renderer {
b.append("Do "+st);
}
if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd())
b.append("Until "+rep.getBoundsPeriod().getEndElement().toHumanDisplay());
b.append("Until "+displayDateTime(rep.getBoundsPeriod().getEndElement()));
}
return b.toString();
}

View File

@ -40,6 +40,7 @@ import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.InstantType;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.Money;
import org.hl7.fhir.r5.model.Narrative;
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
@ -352,6 +353,8 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
renderContactPoint(x, (ContactPoint) e);
} else if (e instanceof Expression) {
renderExpression(x, (Expression) e);
} else if (e instanceof Money) {
renderMoney(x, (Money) e);
} else if (e instanceof ContactDetail) {
ContactDetail cd = (ContactDetail) e;
if (cd.hasName()) {

View File

@ -1,8 +1,14 @@
package org.hl7.fhir.r5.renderers.utils;
import java.io.IOException;
import java.text.DateFormat;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
@ -117,6 +123,11 @@ public class RenderingContext {
private boolean addGeneratedNarrativeHeader = true;
private FhirPublication targetVersion;
private Locale locale;
private ZoneId timeZoneId;
private DateTimeFormatter dateTimeFormat;
private DateTimeFormatter dateFormat;
/**
*
* @param context - access to all related resources that might be needed
@ -136,6 +147,8 @@ public class RenderingContext {
if (terminologyServiceOptions != null) {
this.terminologyServiceOptions = terminologyServiceOptions;
}
// default to US locale - discussion here: https://github.com/hapifhir/org.hl7.fhir.core/issues/666
this.locale = new Locale.Builder().setLanguageTag("en-US").build();
profileUtilities = new ProfileUtilities(worker, null, null);
}
@ -427,4 +440,79 @@ public class RenderingContext {
return mode == ResourceRendererMode.TECHNICAL;
}
public boolean hasLocale() {
return locale != null;
}
public Locale getLocale() {
if (locale == null) {
return Locale.getDefault();
} else {
return locale;
}
}
public void setLocale(Locale locale) {
this.locale = locale;
}
/**
* if the timezone is null, the rendering will default to the source timezone
* in the resource
*
* Note that if you're working server side, the FHIR project recommends the use
* of the Date header so that clients know what timezone the server defaults to,
*
* There is no standard way for the server to know what the client timezone is.
* In the case where the client timezone is unknown, the timezone should be null
*
* @return the specified timezone to render in
*/
public ZoneId getTimeZoneId() {
return timeZoneId;
}
public void setTimeZoneId(ZoneId timeZoneId) {
this.timeZoneId = timeZoneId;
}
/**
* In the absence of a specified format, the renderers will default to
* the FormatStyle.MEDIUM for the current locale.
*
* @return the format to use
*/
public DateTimeFormatter getDateTimeFormat() {
return this.dateTimeFormat;
}
public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) {
this.dateTimeFormat = dateTimeFormat;
}
/**
* In the absence of a specified format, the renderers will default to
* the FormatStyle.MEDIUM for the current locale.
*
* @return the format to use
*/
public DateTimeFormatter getDateFormat() {
return this.dateFormat;
}
public void setDateFormat(DateTimeFormatter dateFormat) {
this.dateFormat = dateFormat;
}
public ResourceRendererMode getMode() {
return mode;
}
public void setMode(ResourceRendererMode mode) {
this.mode = mode;
}
}

View File

@ -151,13 +151,14 @@ public class TestingUtilities extends BaseTestingUtilities {
String result = compareXml(f1, f2);
if (result != null && SHOW_DIFF) {
String diff = Utilities.path(System.getenv("ProgramFiles"), "WinMerge", "WinMergeU.exe");
List<String> command = new ArrayList<String>();
command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\"");
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile("c:\\temp"));
builder.start();
if (new File(diff).exists()) {
List<String> command = new ArrayList<String>();
command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\"");
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(new CSFile("c:\\temp"));
builder.start();
}
}
return result;
}
@ -171,7 +172,7 @@ public class TestingUtilities extends BaseTestingUtilities {
}
private static String compareElements(String path, Element e1, Element e2) {
if (!e1.getNamespaceURI().equals(e2.getNamespaceURI()))
if (!namespacesMatch(e1.getNamespaceURI(), e2.getNamespaceURI()))
return "Namespaces differ at " + path + ": " + e1.getNamespaceURI() + "/" + e2.getNamespaceURI();
if (!e1.getLocalName().equals(e2.getLocalName()))
return "Names differ at " + path + ": " + e1.getLocalName() + "/" + e2.getLocalName();
@ -209,6 +210,10 @@ public class TestingUtilities extends BaseTestingUtilities {
return null;
}
private static boolean namespacesMatch(String ns1, String ns2) {
return ns1 == null ? ns2 == null : ns1.equals(ns2);
}
private static Object normalise(String text) {
String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' ');
while (result.contains(" "))

View File

@ -133,6 +133,11 @@ public class XVerExtensionManager {
} else {
throw new FHIRException("Internal error - attempt to define extension for "+url+" when it is invalid");
}
if (path.has("modifier") && path.get("modifier").getAsBoolean()) {
ElementDefinition baseDef = new ElementDefinition("Extension");
sd.getDifferential().getElement().add(0, baseDef);
baseDef.setIsModifier(true);
}
return sd;
}

View File

@ -1,8 +1,13 @@
package org.hl7.fhir.r5.test;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@ -56,7 +61,6 @@ public class NarrativeGenerationTests {
public Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException {
return new org.hl7.fhir.r5.formats.XmlParser().parseType(xml, type);
}
}
public static final String WINDOWS = "WINDOWS";
@ -77,12 +81,14 @@ public class NarrativeGenerationTests {
private String id;
private boolean header;
private boolean meta;
private boolean technical;
public TestDetails(Element test) {
super();
id = test.getAttribute("id");
header = "true".equals(test.getAttribute("header"));
meta = "true".equals(test.getAttribute("meta"));
technical = "technical".equals(test.getAttribute("mode"));
}
public String getId() {
@ -133,6 +139,15 @@ public class NarrativeGenerationTests {
rc.setDefinitionsTarget("test.html");
rc.setTerminologyServiceOptions(TerminologyServiceOptions.defaults());
rc.setParser(new TestTypeParser());
// getting timezones correct (well, at least consistent, so tests pass on any computer)
rc.setLocale(new java.util.Locale("en", "AU"));
rc.setTimeZoneId(ZoneId.of("Australia/Sydney"));
rc.setDateTimeFormat(null);
rc.setDateFormat(null);
rc.setMode(test.technical ? ResourceRendererMode.TECHNICAL : ResourceRendererMode.END_USER);
Resource source;
if (TestingUtilities.findTestResource("r5", "narrative", test.getId() + ".json")) {
source = (Resource) new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".json"));
@ -143,9 +158,12 @@ public class NarrativeGenerationTests {
XhtmlNode x = RendererFactory.factory(source, rc).build(source);
String target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".html"));
String output = HEADER+new XhtmlComposer(true, true).compose(x)+FOOTER;
TextFile.stringToFile(target, TestingUtilities.tempFile("narrative", test.getId() + ".target.html"));
TextFile.stringToFile(output, TestingUtilities.tempFile("narrative", test.getId() + ".output.html"));
Assertions.assertTrue(output.equals(target), "Output does not match expected");
String tfn = TestingUtilities.tempFile("narrative", test.getId() + ".target.html");
String ofn = TestingUtilities.tempFile("narrative", test.getId() + ".output.html");
TextFile.stringToFile(target, tfn);
TextFile.stringToFile(output, ofn);
String msg = TestingUtilities.checkXMLIsSame(ofn, tfn);
Assertions.assertTrue(msg == null, "Output does not match expected: "+msg);
if (test.isMeta()) {
org.hl7.fhir.r5.elementmodel.Element e = Manager.parseSingle(context, TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + ".xml"), FhirFormat.XML);
@ -153,8 +171,10 @@ public class NarrativeGenerationTests {
target = TextFile.streamToString(TestingUtilities.loadTestResourceStream("r5", "narrative", test.getId() + "-meta.html"));
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)");
ofn = TestingUtilities.tempFile("narrative", test.getId() + "-meta.output.html");
TextFile.stringToFile(output, ofn);
msg = TestingUtilities.checkXMLIsSame(ofn, tfn);
Assertions.assertTrue(msg == null, "Meta output does not match expected: "+msg);
}
}

View File

@ -4,22 +4,34 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import java.util.TimeZone;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.renderers.DataRenderer;
import org.hl7.fhir.r5.renderers.RendererFactory;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.xmlpull.v1.XmlPullParserException;
public class NarrativeGeneratorTests {
private static RenderingContext rc;
@BeforeAll
@ -40,4 +52,58 @@ public class NarrativeGeneratorTests {
new XmlParser().compose(s, r, true);
s.close();
}
private void checkDateTimeRendering(String src, String lang, String country, ZoneId tz, FormatStyle fmt, ResourceRendererMode mode, String expected) throws FHIRFormatError, DefinitionException, IOException {
rc.setLocale(new java.util.Locale(lang, country));
rc.setTimeZoneId(tz);
if (fmt == null) {
rc.setDateTimeFormat(null);
rc.setDateFormat(null);
} else {
rc.setDateTimeFormat(DateTimeFormatter.ofLocalizedDateTime(fmt).withLocale(rc.getLocale()));
rc.setDateFormat(DateTimeFormatter.ofLocalizedDate(fmt).withLocale(rc.getLocale()));
}
rc.setMode(mode);
DateTimeType dt = new DateTimeType(src);
String actual = new DataRenderer(rc).display(dt);
Assert.assertEquals(expected, actual);
XhtmlNode node = new XhtmlNode(NodeType.Element, "p");
new DataRenderer(rc).render(node, dt);
actual = new XhtmlComposer(true, false).compose(node);
Assert.assertEquals("<p>"+expected+"</p>", actual);
}
@Test
public void testDateTimeRendering1() throws FHIRFormatError, DefinitionException, IOException {
checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.TECHNICAL, "2021-11-19T14:13:12Z");
}
@Test
public void testDateTimeRendering2() throws FHIRFormatError, DefinitionException, IOException {
checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("Australia/Sydney"), null, ResourceRendererMode.TECHNICAL, "2021-11-20T01:13:12+11:00");
}
//
// @Test
// public void testDateTimeRendering3() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), FormatStyle.SHORT, ResourceRendererMode.TECHNICAL, "19/11/21, 2:13 pm");
// }
//
//
// @Test
// public void testDateTimeRendering4() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19T14:13:12Z", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21, 2:13 pm");
// }
//
//
// @Test
// public void testDateTimeRendering5() throws FHIRFormatError, DefinitionException, IOException {
// checkDateTimeRendering("2021-11-19", "en", "AU", ZoneId.of("UTC"), null, ResourceRendererMode.END_USER, "19/11/21");
// }
//
}

View File

@ -664,6 +664,12 @@ public class JsonTrackingParser {
TextFile.stringToFile(jcnt, file);
}
public static void write(JsonObject json, File file, boolean pretty) throws IOException {
Gson gson = pretty ? new GsonBuilder().setPrettyPrinting().create() : new GsonBuilder().create();
String jcnt = gson.toJson(json);
TextFile.stringToFile(jcnt, file);
}
public static void write(JsonObject json, String fileName) throws IOException {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String jcnt = gson.toJson(json);

View File

@ -3,7 +3,7 @@ package org.hl7.fhir.utilities.npm;
public class CommonPackages {
public static final String ID_XVER = "hl7.fhir.xver-extensions";
public static final String VER_XVER = "0.0.7";
public static final String VER_XVER = "0.0.8";
public static final String ID_PUBPACK = "hl7.fhir.pubpack";
public static final String VER_PUBPACK = "0.0.9";

View File

@ -30,7 +30,7 @@ class UtilitiesTest {
public static final String WIN_JAVA_HOME = System.getenv("JAVA_HOME") + "\\";
public static final String OSX_USER_DIR = System.getProperty("user.home") + "/";
public static final String OSX_JAVA_HOME = Paths.get(System.getenv("JAVA_HOME")).normalize().toString() + "/";
public static final String OSX_JAVA_HOME = System.getenv("JAVA_HOME") == null ? null : Paths.get(System.getenv("JAVA_HOME")).normalize().toString() + "/";
@Test
@DisplayName("Test Utilities.path maps temp directory correctly")

View File

@ -1476,7 +1476,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private String describeValueSet(String url) {
ValueSet vs = context.fetchResource(ValueSet.class, url);
if (vs != null) {
return "\""+vs.present()+"\" ("+url+")";
return "'"+vs.present()+"' ("+url+")";
} else {
return "("+url+")";
}
@ -1656,7 +1656,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private StructureDefinition checkExtension(ValidatorHostContext hostContext, List<ValidationMessage> errors, String path, Element resource, Element container, Element element, ElementDefinition def, StructureDefinition profile, NodeStack stack, NodeStack containerStack, String extensionUrl) throws FHIRException {
String url = element.getNamedChildValue("url");
boolean isModifier = element.getName().equals("modifierExtension");
assert def.getIsModifier() == isModifier;
long t = System.nanoTime();
StructureDefinition ex = Utilities.isAbsoluteUrl(url) ? context.fetchResource(StructureDefinition.class, url) : null;
timeTracker.sd(t);
@ -1676,10 +1677,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
}
if (ex != null) {
trackUsage(ex, hostContext, element);
if (def.getIsModifier()) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY);
// check internal definitions are coherent
if (isModifier) {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHY);
} else {
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", !ex.getSnapshot().getElement().get(0).getIsModifier(), I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN);
rule(errors, IssueType.STRUCTURE, element.line(), element.col(), path + "[url='" + url + "']", def.getIsModifier() == isModifier, I18nConstants.EXTENSION_EXT_MODIFIER_MISMATCHN);
}
// two questions
// 1. can this extension be used here?

View File

@ -19,7 +19,7 @@
<properties>
<hapi_fhir_version>5.1.0</hapi_fhir_version>
<validator_test_case_version>1.1.74</validator_test_case_version>
<validator_test_case_version>1.1.77</validator_test_case_version>
<junit_jupiter_version>5.7.1</junit_jupiter_version>
<junit_platform_launcher_version>1.7.1</junit_platform_launcher_version>
<maven_surefire_version>3.0.0-M5</maven_surefire_version>