rewrite the way language works in value sets
This commit is contained in:
parent
d468a61664
commit
7eaa723ec9
|
@ -568,10 +568,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
}
|
||||
|
||||
private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
|
||||
if (options != null) {
|
||||
for (String s : options.getLanguages()) {
|
||||
pIn.addParameter("displayLanguage", s);
|
||||
}
|
||||
if (options != null && options.hasLanguages()) {
|
||||
pIn.addParameter("displayLanguage", options.getLanguages().toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1048,8 +1048,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
}
|
||||
|
||||
private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
|
||||
for (String s : options.getLanguages()) {
|
||||
pIn.addParameter("displayLanguage", s);
|
||||
if (options.hasLanguages()) {
|
||||
pIn.addParameter("displayLanguage", options.getLanguages().toString());
|
||||
}
|
||||
if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) {
|
||||
pIn.addParameter("valueSetMode", options.getValueSetMode().toString());
|
||||
|
|
|
@ -1326,8 +1326,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
|
|||
}
|
||||
|
||||
private void setTerminologyOptions(ValidationOptions options, Parameters pIn) {
|
||||
for (String s : options.getLanguages()) {
|
||||
pIn.addParameter("displayLanguage", s);
|
||||
if (options.hasLanguages()) {
|
||||
pIn.addParameter("displayLanguage", options.getLanguages().toString());
|
||||
}
|
||||
if (options.getValueSetMode() != ValueSetMode.ALL_CHECKS) {
|
||||
pIn.addParameter("valueSetMode", options.getValueSetMode().toString());
|
||||
|
|
|
@ -18,6 +18,8 @@ import org.hl7.fhir.r5.model.Resource;
|
|||
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
|
||||
import org.hl7.fhir.utilities.TextFile;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference;
|
||||
import org.hl7.fhir.utilities.i18n.LanguageFileProducer;
|
||||
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession;
|
||||
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit;
|
||||
|
@ -201,10 +203,49 @@ public class LanguageUtils {
|
|||
return res;
|
||||
}
|
||||
|
||||
public static boolean langsMatch(String dstLang, String srcLang) {
|
||||
public static boolean langsMatchExact(AcceptLanguageHeader langs, String srcLang) {
|
||||
if (langs == null) {
|
||||
return false;
|
||||
}
|
||||
for (LanguagePreference lang : langs.getLangs()) {
|
||||
if (lang.getValue() > 0) {
|
||||
if ("*".equals(lang.getLang())) {
|
||||
return true;
|
||||
} else {
|
||||
return langsMatch(lang.getLang(), srcLang);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean langsMatch(AcceptLanguageHeader langs, String srcLang) {
|
||||
if (langs == null) {
|
||||
return false;
|
||||
}
|
||||
for (LanguagePreference lang : langs.getLangs()) {
|
||||
if (lang.getValue() > 0) {
|
||||
if ("*".equals(lang.getLang())) {
|
||||
return true;
|
||||
} else {
|
||||
boolean ok = langsMatch(lang.getLang(), srcLang);
|
||||
if (ok) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean langsMatchExact(String dstLang, String srcLang) {
|
||||
return dstLang == null ? false : dstLang.equals(srcLang);
|
||||
}
|
||||
|
||||
public static boolean langsMatch(String dstLang, String srcLang) {
|
||||
return dstLang == null ? false : dstLang.startsWith(srcLang) || "*".equals(srcLang);
|
||||
}
|
||||
|
||||
public static void fillSupplement(CodeSystem cs, List<TranslationUnit> list) {
|
||||
cs.setUserData(SUPPLEMENT_NAME, "true");
|
||||
for (TranslationUnit tu : list) {
|
||||
|
|
|
@ -87,11 +87,14 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
|||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
|
||||
import org.hl7.fhir.r5.model.CodeType;
|
||||
import org.hl7.fhir.r5.model.Coding;
|
||||
import org.hl7.fhir.r5.model.DataType;
|
||||
import org.hl7.fhir.r5.model.DateTimeType;
|
||||
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
|
||||
import org.hl7.fhir.r5.model.Extension;
|
||||
import org.hl7.fhir.r5.model.Factory;
|
||||
import org.hl7.fhir.r5.model.IntegerType;
|
||||
import org.hl7.fhir.r5.model.PackageInformation;
|
||||
import org.hl7.fhir.r5.model.Parameters;
|
||||
import org.hl7.fhir.r5.model.Parameters.ParametersParameterComponent;
|
||||
|
@ -112,6 +115,7 @@ import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionParameterComponent;
|
|||
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionPropertyComponent;
|
||||
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
|
||||
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
|
||||
import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpander.Token;
|
||||
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProvider;
|
||||
import org.hl7.fhir.r5.terminologies.providers.CodeSystemProviderExtension;
|
||||
import org.hl7.fhir.r5.terminologies.utilities.TerminologyOperationContext;
|
||||
|
@ -121,11 +125,35 @@ import org.hl7.fhir.r5.terminologies.utilities.ValueSetProcessBase;
|
|||
import org.hl7.fhir.r5.utils.ToolingExtensions;
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
|
||||
public class ValueSetExpander extends ValueSetProcessBase {
|
||||
|
||||
|
||||
public class Token {
|
||||
private String system;
|
||||
private String code;
|
||||
public Token(String system, String code) {
|
||||
super();
|
||||
this.system = system;
|
||||
this.code = code;
|
||||
}
|
||||
public String getSystem() {
|
||||
return system;
|
||||
}
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
public boolean matches(Coding use) {
|
||||
return system.equals(use.getSystem()) && code.equals(use.getCode());
|
||||
}
|
||||
public boolean matchesLang(String language) {
|
||||
return system.equals("urn:ietf:bcp:47") && code.equals(language);
|
||||
}
|
||||
}
|
||||
|
||||
private static final boolean REPORT_VERSION_ANYWAY = true;
|
||||
|
||||
private ValueSet focus;
|
||||
|
@ -136,6 +164,9 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
private boolean checkCodesWhenExpanding;
|
||||
private boolean includeAbstract = true;
|
||||
|
||||
private AcceptLanguageHeader langs;
|
||||
private List<Token> designations = new ArrayList<>();
|
||||
|
||||
public ValueSetExpander(IWorkerContext context, TerminologyOperationContext opContext) {
|
||||
super(context, opContext);
|
||||
}
|
||||
|
@ -203,31 +234,24 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
"http://hl7.org/fhir/StructureDefinition/rendering-xhtml");
|
||||
|
||||
// display and designations
|
||||
String srcLang = dispLang;
|
||||
String dstLang = focus.getLanguage();
|
||||
|
||||
boolean usedDisplay = false;
|
||||
ConceptDefinitionDesignationComponent tu;
|
||||
if (LanguageUtils.langsMatch(dstLang, dispLang)) {
|
||||
tu = null; // use display
|
||||
} else {
|
||||
tu = expParams.hasParameter("displayLanguage") ? getMatchingLang(designations, expParams.getParameterString("displayLanguage")) : null;
|
||||
}
|
||||
if (tu != null) {
|
||||
n.setDisplay(tu.getValue());
|
||||
} else if (display != null && (srcLang == null || dstLang == null || LanguageUtils.langsMatch(dstLang, srcLang))) {
|
||||
ConceptDefinitionDesignationComponent pref = null;
|
||||
if (langs == null) {
|
||||
n.setDisplay(display);
|
||||
usedDisplay = true;
|
||||
} else {
|
||||
// we don't have a usable display
|
||||
if (designations == null) {
|
||||
designations = new ArrayList<>();
|
||||
}
|
||||
designations.add(new ConceptDefinitionDesignationComponent().setLanguage(dispLang).setValue(display).setUse(new Coding().setSystem("http://terminology.hl7.org/CodeSystem/designation-usage").setCode("display")));
|
||||
pref = findMatchingDesignation(designations);
|
||||
if (pref != null) {
|
||||
n.setDisplay(pref.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
if (expParams.getParameterBool("includeDesignations") && designations != null) {
|
||||
if (!usedDisplay && display != null) {
|
||||
n.addDesignation().setLanguage(srcLang).setValue(display);
|
||||
}
|
||||
if (expParams.getParameterBool("includeDesignations")) {
|
||||
|
||||
for (ConceptDefinitionDesignationComponent t : designations) {
|
||||
if (t != tu && (t.hasLanguage() || t.hasUse()) && t.getValue() != null) {
|
||||
if (t != pref && (t.hasLanguage() || t.hasUse()) && t.getValue() != null && passesDesignationFilter(t)) {
|
||||
ConceptReferenceDesignationComponent d = n.addDesignation();
|
||||
if (t.getLanguage() != null) {
|
||||
d.setLanguage(t.getLanguage().trim());
|
||||
|
@ -288,6 +312,63 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
return n;
|
||||
}
|
||||
|
||||
private boolean passesDesignationFilter(ConceptDefinitionDesignationComponent d) {
|
||||
if (designations.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
for (Token t : designations) {
|
||||
if (t.matches(d.getUse()) || t.matchesLang(d.getLanguage())) {
|
||||
return true;
|
||||
}
|
||||
for (Coding c : d.getAdditionalUse()) {
|
||||
if (t.matches(c)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ConceptDefinitionDesignationComponent findMatchingDesignation(List<ConceptDefinitionDesignationComponent> designations) {
|
||||
if (langs == null) {
|
||||
return null;
|
||||
}
|
||||
// we have a list of languages in priority order
|
||||
// we have a list of designations in no order
|
||||
// language exact match is preferred
|
||||
// display is always preferred
|
||||
|
||||
for (LanguagePreference lang : langs.getLangs()) {
|
||||
if (lang.getValue() > 0) {
|
||||
for (ConceptDefinitionDesignationComponent cd : designations) {
|
||||
if (isDisplay(cd) && LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent cd : designations) {
|
||||
if (isDisplay(cd) && LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent cd : designations) {
|
||||
if (LanguageUtils.langsMatchExact(cd.getLanguage(), lang.getLang())) {
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent cd : designations) {
|
||||
if (LanguageUtils.langsMatch(cd.getLanguage(), lang.getLang())) {
|
||||
return cd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isDisplay(ConceptDefinitionDesignationComponent cd) {
|
||||
return cd.getUse().is("http://terminology.hl7.org/CodeSystem/designation-usage", "display");
|
||||
}
|
||||
|
||||
private boolean filterContainsCode(List<ValueSet> filters, String system, String code, ValueSetExpansionComponent exp) {
|
||||
for (ValueSet vse : filters) {
|
||||
checkCanonical(exp, vse, focus);
|
||||
|
@ -307,13 +388,17 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
return false;
|
||||
}
|
||||
|
||||
private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, String lang) {
|
||||
for (ConceptDefinitionDesignationComponent t : list)
|
||||
if (t.getLanguage().equals(lang))
|
||||
private ConceptDefinitionDesignationComponent getMatchingLang(List<ConceptDefinitionDesignationComponent> list, AcceptLanguageHeader langs) {
|
||||
for (ConceptDefinitionDesignationComponent t : list) {
|
||||
if (LanguageUtils.langsMatchExact(langs, t.getLanguage())) {
|
||||
return t;
|
||||
for (ConceptDefinitionDesignationComponent t : list)
|
||||
if (t.getLanguage().startsWith(lang))
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent t : list) {
|
||||
if (LanguageUtils.langsMatch(langs, t.getLanguage())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -519,6 +604,42 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
return doExpand(source, expParams);
|
||||
}
|
||||
|
||||
private void processParameter(String name, DataType value) {
|
||||
if (Utilities.existsInList(name, "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
|
||||
focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
|
||||
focus.getExpansion().addParameter().setName(name).setValue(value);
|
||||
}
|
||||
if ("displayLanguage".equals(name)) {
|
||||
this.langs = new AcceptLanguageHeader(value.primitiveValue(), true);
|
||||
focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
|
||||
focus.getExpansion().addParameter().setName(name).setValue(new CodeType(value.primitiveValue()));
|
||||
}
|
||||
if ("designation".equals(name)) {
|
||||
String[] v = value.primitiveValue().split("\\|");
|
||||
if (v.length != 2 || !Utilities.isAbsoluteUrl(v[0]) || Utilities.noString(v[1])) {
|
||||
throw new NoTerminologyServiceException("Unable to understand designation parameter "+value.primitiveValue());
|
||||
}
|
||||
this.designations.add(new Token(v[0], v[1]));
|
||||
focus.getExpansion().addParameter().setName(name).setValue(new StringType(value.primitiveValue()));
|
||||
}
|
||||
if ("offset".equals(name) && value instanceof IntegerType) {
|
||||
focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
|
||||
focus.getExpansion().addParameter().setName(name).setValue(value);
|
||||
dwc.setOffset(((IntegerType) value).getValue());
|
||||
if (dwc.getOffset() < 0) {
|
||||
dwc.setOffset(0);
|
||||
}
|
||||
}
|
||||
if ("count".equals(name)) {
|
||||
focus.getExpansion().getParameter().removeIf(p -> p.getName().equals(name));
|
||||
focus.getExpansion().addParameter().setName(name).setValue(value);
|
||||
dwc.setCount(((IntegerType) value).getValue());
|
||||
if (dwc.getCount() < 0) {
|
||||
dwc.setCount(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValueSetExpansionOutcome doExpand(ValueSet source, Parameters expParams) throws FHIRException, ETooCostly, FileNotFoundException, IOException, CodeSystemProviderExtension {
|
||||
if (expParams == null)
|
||||
expParams = makeDefaultExpansion();
|
||||
|
@ -532,34 +653,21 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
focus.getExpansion().setTimestampElement(DateTimeType.now());
|
||||
focus.getExpansion().setIdentifier(Factory.createUUID());
|
||||
checkCanonical(focus.getExpansion(), focus, focus);
|
||||
for (Extension ext : focus.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) {
|
||||
processParameter(ext.getExtensionString("name"), ext.getExtensionByUrl("value").getValue());
|
||||
}
|
||||
for (ParametersParameterComponent p : expParams.getParameter()) {
|
||||
if (Utilities.existsInList(p.getName(), "includeDesignations", "excludeNested", "activeOnly", "offset", "count")) {
|
||||
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
|
||||
}
|
||||
if ("displayLanguage".equals(p.getName()) && (!expParams.hasLanguage() || !expParams.getLanguage().equals(p.getValue().primitiveValue()))) {
|
||||
focus.getExpansion().addParameter().setName(p.getName()).setValue(p.getValue());
|
||||
}
|
||||
if ("offset".equals(p.getName()) && p.hasValueIntegerType()) {
|
||||
dwc.setOffset(p.getValueIntegerType().getValue());
|
||||
if (dwc.getOffset() < 0) {
|
||||
dwc.setOffset(0);
|
||||
}
|
||||
}
|
||||
if ("count".equals(p.getName()) && p.hasValueIntegerType()) {
|
||||
dwc.setCount(p.getValueIntegerType().getValue());
|
||||
if (dwc.getCount() < 0) {
|
||||
dwc.setCount(0);
|
||||
}
|
||||
}
|
||||
processParameter(p.getName(), p.getValue());
|
||||
}
|
||||
for (Extension s : focus.getExtensionsByUrl(ExtensionConstants.EXT_VSSUPPLEMENT)) {
|
||||
requiredSupplements.add(s.getValue().primitiveValue());
|
||||
}
|
||||
if (expParams.hasLanguage()) {
|
||||
focus.setLanguage(expParams.getLanguage());
|
||||
if (langs == null && focus.hasLanguage()) {
|
||||
langs = new AcceptLanguageHeader(focus.getLanguage(), true);
|
||||
} else if (langs != null && langs.hasChosen()) {
|
||||
focus.setLanguage(langs.getChosen());
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (source.hasCompose()) {
|
||||
// ExtensionsUtils.stripExtensions(focus.getCompose()); - disabled 23/05/2023 GDG - why was this ever thought to be a good idea?
|
||||
|
@ -610,6 +718,7 @@ public class ValueSetExpander extends ValueSetProcessBase {
|
|||
return new ValueSetExpansionOutcome(focus);
|
||||
}
|
||||
|
||||
|
||||
private Parameters makeDefaultExpansion() {
|
||||
Parameters res = new Parameters();
|
||||
res.addParameter("excludeNested", true);
|
||||
|
|
|
@ -47,9 +47,11 @@ import org.hl7.fhir.exceptions.NoTerminologyServiceException;
|
|||
import org.hl7.fhir.r5.context.ContextUtilities;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext;
|
||||
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
|
||||
import org.hl7.fhir.r5.elementmodel.LanguageUtils;
|
||||
import org.hl7.fhir.r5.extensions.ExtensionConstants;
|
||||
import org.hl7.fhir.r5.model.CanonicalType;
|
||||
import org.hl7.fhir.r5.model.CodeSystem;
|
||||
import org.hl7.fhir.r5.model.CodeType;
|
||||
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
|
||||
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
|
||||
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
|
||||
|
@ -90,6 +92,7 @@ import org.hl7.fhir.r5.utils.validation.ValidationContextCarrier.ValidationConte
|
|||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.VersionUtilities;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
|
||||
import org.hl7.fhir.utilities.i18n.I18nConstants;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
import org.hl7.fhir.utilities.validation.ValidationOptions;
|
||||
|
@ -191,6 +194,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
|
||||
public ValidationResult validateCode(String path, CodeableConcept code) throws FHIRException {
|
||||
opContext.deadCheck();
|
||||
checkValueSetOptions();
|
||||
|
||||
// first, we validate the codings themselves
|
||||
ValidationProcessInfo info = new ValidationProcessInfo();
|
||||
|
@ -370,6 +374,8 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
|
||||
public ValidationResult validateCode(String path, Coding code) throws FHIRException {
|
||||
opContext.deadCheck();
|
||||
checkValueSetOptions();
|
||||
|
||||
String warningMessage = null;
|
||||
// first, we validate the concept itself
|
||||
|
||||
|
@ -550,6 +556,21 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
return res;
|
||||
}
|
||||
|
||||
private void checkValueSetOptions() {
|
||||
if (valueset != null) {
|
||||
for (Extension ext : valueset.getCompose().getExtensionsByUrl("http://hl7.org/fhir/tools/StructureDefinion/valueset-expansion-param")) {
|
||||
var name = ext.getExtensionString("name");
|
||||
var value = ext.getExtensionByUrl("value").getValue();
|
||||
if ("displayLanguage".equals(name)) {
|
||||
options.setLanguages(value.primitiveValue());
|
||||
}
|
||||
}
|
||||
if (!options.hasLanguages() && valueset.hasLanguage()) {
|
||||
options.addLanguage(valueset.getLanguage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Set<String> SERVER_SIDE_LIST = new HashSet<>(Arrays.asList("http://fdasis.nlm.nih.gov", "http://hl7.org/fhir/sid/ndc", "http://loinc.org", "http://snomed.info/sct", "http://unitsofmeasure.org",
|
||||
"http://unstats.un.org/unsd/methods/m49/m49.htm", "http://varnomen.hgvs.org", "http://www.nlm.nih.gov/research/umls/rxnorm", "https://www.usps.com/",
|
||||
"urn:ietf:bcp:13","urn:ietf:bcp:47","urn:ietf:rfc:3986", "urn:iso:std:iso:3166","urn:iso:std:iso:4217", "urn:oid:1.2.36.1.2001.1005.17"));
|
||||
|
@ -736,7 +757,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
}
|
||||
}
|
||||
if (b.count() == 0) {
|
||||
String msg = context.formatMessagePlural(options.getLanguages().size(), I18nConstants.NO_VALID_DISPLAY_FOUND, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary());
|
||||
String msg = context.formatMessagePlural(options.getLanguages().getLangs().size(), I18nConstants.NO_VALID_DISPLAY_FOUND, code.getSystem(), code.getCode(), code.getDisplay(), options.langSummary());
|
||||
return new ValidationResult(IssueSeverity.WARNING, msg, code.getSystem(), cs.getVersion(), cc, getPreferredDisplay(cc, cs), makeIssue(IssueSeverity.WARNING, IssueType.INVALID, path+".display", msg)).setStatus(inactive, status);
|
||||
} else {
|
||||
String msg = context.formatMessagePlural(b.count(), ws ? I18nConstants.DISPLAY_NAME_WS_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF : I18nConstants.DISPLAY_NAME_FOR__SHOULD_BE_ONE_OF__INSTEAD_OF, code.getSystem(), code.getCode(), b.toString(), code.getDisplay(), options.langSummary());
|
||||
|
@ -756,10 +777,10 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
if (!options.hasLanguages()) {
|
||||
return true;
|
||||
}
|
||||
if (options.getLanguages().contains(language)) {
|
||||
if (LanguageUtils.langsMatch(options.getLanguages(), language)) {
|
||||
return true;
|
||||
}
|
||||
if (language == null && (options.getLanguages().contains("en") || options.getLanguages().contains("en-US") || options.isEnglishOk())) {
|
||||
if (language == null && (options.langSummary().contains("en") || options.langSummary().contains("en-US") || options.isEnglishOk())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -1285,20 +1306,20 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
if (!options.hasLanguages()) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
if (options.getLanguages().contains(valueset.getLanguage())) {
|
||||
if (LanguageUtils.langsMatch(options.getLanguages(), valueset.getLanguage())) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
// if there's no language, we default to accepting the displays as (US) English
|
||||
if (valueset.getLanguage() == null && (options.getLanguages().contains("en") || options.getLanguages().contains("en-US"))) {
|
||||
if (valueset.getLanguage() == null && (options.langSummary().contains("en") || options.langSummary().contains("en-US"))) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
for (ConceptReferenceDesignationComponent d : cc.getDesignation()) {
|
||||
if (!d.hasUse() && options.getLanguages().contains(d.getLanguage())) {
|
||||
if (!d.hasUse() && LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) {
|
||||
return d.getValue();
|
||||
}
|
||||
}
|
||||
for (ConceptReferenceDesignationComponent d : cc.getDesignation()) {
|
||||
if (options.getLanguages().contains(d.getLanguage())) {
|
||||
if (LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) {
|
||||
return d.getValue();
|
||||
}
|
||||
}
|
||||
|
@ -1310,20 +1331,20 @@ public class ValueSetValidator extends ValueSetProcessBase {
|
|||
if (!options.hasLanguages()) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
if (cs != null && options.getLanguages().contains(cs.getLanguage())) {
|
||||
if (cs != null && LanguageUtils.langsMatch(options.getLanguages(), cs.getLanguage())) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
// if there's no language, we default to accepting the displays as (US) English
|
||||
if ((cs == null || cs.getLanguage() == null) && (options.getLanguages().contains("en") || options.getLanguages().contains("en-US"))) {
|
||||
if ((cs == null || cs.getLanguage() == null) && (options.langSummary().contains("en") || options.langSummary().contains("en-US"))) {
|
||||
return cc.getDisplay();
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) {
|
||||
if (!d.hasUse() && options.getLanguages().contains(d.getLanguage())) {
|
||||
if (!d.hasUse() && LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) {
|
||||
return d.getValue();
|
||||
}
|
||||
}
|
||||
for (ConceptDefinitionDesignationComponent d : cc.getDesignation()) {
|
||||
if (options.getLanguages().contains(d.getLanguage())) {
|
||||
if (LanguageUtils.langsMatch(options.getLanguages(), d.getLanguage())) {
|
||||
return d.getValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
package org.hl7.fhir.utilities.i18n;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguageSorter;
|
||||
|
||||
import net.sf.saxon.functions.Lang;
|
||||
|
||||
public class AcceptLanguageHeader {
|
||||
|
||||
public class LanguageSorter implements Comparator<LanguagePreference> {
|
||||
|
||||
@Override
|
||||
public int compare(LanguagePreference o1, LanguagePreference o2) {
|
||||
if (o1.getValue() == o2.getValue()) {
|
||||
return o1.getOrder() - o2.getOrder();
|
||||
} else if (o1.getValue() > o2.getValue()) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class LanguagePreference {
|
||||
private int order;
|
||||
private String lang;
|
||||
private double value;
|
||||
public String getLang() {
|
||||
return lang;
|
||||
}
|
||||
public double getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
public LanguagePreference(int order, String lang, double value) {
|
||||
super();
|
||||
this.order = order;
|
||||
this.lang = lang;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value == 0) {
|
||||
return lang;
|
||||
} else {
|
||||
return lang+"; q="+Double.toString(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String source;
|
||||
private List<LanguagePreference> langs = new ArrayList<>();
|
||||
private boolean doWildcard;
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public List<LanguagePreference> getLangs() {
|
||||
return langs;
|
||||
}
|
||||
|
||||
public AcceptLanguageHeader(String source, boolean doWildcard) {
|
||||
super();
|
||||
this.doWildcard = doWildcard;
|
||||
this.source = source;
|
||||
process();
|
||||
}
|
||||
|
||||
private void process() {
|
||||
langs.clear();
|
||||
String[] parts = source.split("\\,");
|
||||
boolean wildcard = false;
|
||||
for (int i = 0; i < parts.length; i++) {
|
||||
String lang = parts[i].trim();
|
||||
double weight = 1;
|
||||
if (lang.contains(";")) {
|
||||
String w = lang.substring(lang.indexOf(";")+1);
|
||||
if (w.contains("=")) {
|
||||
w = w.substring(w.indexOf("=")+1);
|
||||
}
|
||||
lang = lang.substring(0, lang.indexOf(";"));
|
||||
weight = Float.valueOf(w);
|
||||
}
|
||||
langs.add(new LanguagePreference(i, lang, weight));
|
||||
wildcard = wildcard || "*".equals(lang);
|
||||
}
|
||||
if (!wildcard && doWildcard) {
|
||||
langs.add(new LanguagePreference(100, "*", 0.01));
|
||||
}
|
||||
Collections.sort(langs, new LanguageSorter());
|
||||
|
||||
}
|
||||
|
||||
public boolean hasChosen() {
|
||||
for (LanguagePreference lang : langs) {
|
||||
if (lang.value == 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public String getChosen() {
|
||||
for (LanguagePreference lang : langs) {
|
||||
if (lang.value == 1) {
|
||||
return lang.lang;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void add(String language) {
|
||||
source = toString()+","+language;
|
||||
process();
|
||||
|
||||
}
|
||||
|
||||
public AcceptLanguageHeader copy() {
|
||||
return new AcceptLanguageHeader(toString(), doWildcard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
|
||||
for (LanguagePreference lang : langs) {
|
||||
b.append(lang.toString());
|
||||
}
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -4,13 +4,16 @@ import java.util.HashSet;
|
|||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.utilities.Utilities;
|
||||
import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
public class ValidationOptions {
|
||||
public enum ValueSetMode {
|
||||
ALL_CHECKS, CHECK_MEMERSHIP_ONLY, NO_MEMBERSHIP_CHECK
|
||||
}
|
||||
|
||||
private Set<String> languages = new HashSet<>();
|
||||
private AcceptLanguageHeader langs = null;
|
||||
private boolean useServer = true;
|
||||
private boolean useClient = true;
|
||||
private boolean guessSystem = false;
|
||||
|
@ -21,15 +24,19 @@ public class ValidationOptions {
|
|||
private boolean useValueSetDisplays;
|
||||
private boolean englishOk = true;
|
||||
|
||||
public ValidationOptions(String... languages) {
|
||||
public ValidationOptions() {
|
||||
super();
|
||||
for(String s : languages) {
|
||||
this.languages.add(s);
|
||||
}
|
||||
|
||||
public ValidationOptions(String language) {
|
||||
super();
|
||||
if (!Utilities.noString(language)) {
|
||||
langs = new AcceptLanguageHeader(language, false);
|
||||
}
|
||||
}
|
||||
|
||||
public static ValidationOptions defaults() {
|
||||
return new ValidationOptions("en", "en-US");
|
||||
return new ValidationOptions("en, en-US");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -39,12 +46,12 @@ public class ValidationOptions {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<String> getLanguages() {
|
||||
return languages;
|
||||
public AcceptLanguageHeader getLanguages() {
|
||||
return langs;
|
||||
}
|
||||
|
||||
public boolean hasLanguages() {
|
||||
return languages.size() > 0;
|
||||
return langs != null;
|
||||
}
|
||||
|
||||
|
||||
|
@ -128,7 +135,7 @@ public class ValidationOptions {
|
|||
return this;
|
||||
}
|
||||
ValidationOptions n = this.copy();
|
||||
n.languages.add(language);
|
||||
n.addLanguage(language);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -187,7 +194,16 @@ public class ValidationOptions {
|
|||
}
|
||||
|
||||
public ValidationOptions addLanguage(String language) {
|
||||
this.languages.add(language);
|
||||
if (this.langs == null) {
|
||||
langs = new AcceptLanguageHeader(language, false);
|
||||
} else {
|
||||
langs.add(language);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ValidationOptions setLanguages(String language) {
|
||||
langs = new AcceptLanguageHeader(language, false);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -247,7 +263,7 @@ public class ValidationOptions {
|
|||
|
||||
public ValidationOptions copy() {
|
||||
ValidationOptions n = new ValidationOptions();
|
||||
n.languages.addAll(languages);
|
||||
n.langs = langs == null ? null : langs.copy();
|
||||
n.useServer = useServer;
|
||||
n.useClient = useClient;
|
||||
n.guessSystem = guessSystem;
|
||||
|
@ -261,19 +277,18 @@ public class ValidationOptions {
|
|||
|
||||
|
||||
public String toJson() {
|
||||
return "\"langs\":\""+languages.toString()+"\", \"useServer\":\""+Boolean.toString(useServer)+"\", \"useClient\":\""+Boolean.toString(useClient)+"\", "+
|
||||
return "\"langs\":\""+( langs == null ? "" : langs.toString())+"\", \"useServer\":\""+Boolean.toString(useServer)+"\", \"useClient\":\""+Boolean.toString(useClient)+"\", "+
|
||||
"\"guessSystem\":\""+Boolean.toString(guessSystem)+"\", \"valueSetMode\":\""+valueSetMode.toString()+"\", \"displayWarningMode\":\""+Boolean.toString(displayWarningMode)+"\", \"versionFlexible\":\""+Boolean.toString(versionFlexible)+"\"";
|
||||
}
|
||||
|
||||
public String langSummary() {
|
||||
if (languages.size() == 0) {
|
||||
if (langs == null) {
|
||||
return "--";
|
||||
} else {
|
||||
return String.join("|", Utilities.sorted(languages));
|
||||
return langs.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package org.hl7.fhir.utilities.i18n;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class AcceptLanguageTests {
|
||||
|
||||
@Test
|
||||
public void testEasy() {
|
||||
AcceptLanguageHeader hdr = new AcceptLanguageHeader("en", true);
|
||||
Assertions.assertEquals(1, hdr.getLangs().size());
|
||||
Assertions.assertEquals(1, hdr.getLangs().get(0).getValue());
|
||||
Assertions.assertEquals(0, hdr.getLangs().get(0).getOrder());
|
||||
Assertions.assertEquals("en", hdr.getLangs().get(0).getLang());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testComplex() {
|
||||
AcceptLanguageHeader hdr = new AcceptLanguageHeader("en; q=0.9, de;q=0.7, *", true);
|
||||
Assertions.assertEquals(3, hdr.getLangs().size());
|
||||
|
||||
Assertions.assertEquals(1, hdr.getLangs().get(0).getValue());
|
||||
Assertions.assertEquals(2, hdr.getLangs().get(0).getOrder());
|
||||
Assertions.assertEquals("*", hdr.getLangs().get(0).getLang());
|
||||
|
||||
Assertions.assertEquals(0.9, hdr.getLangs().get(1).getValue(), 0.001);
|
||||
Assertions.assertEquals(0, hdr.getLangs().get(1).getOrder());
|
||||
Assertions.assertEquals("en", hdr.getLangs().get(1).getLang());
|
||||
|
||||
Assertions.assertEquals(0.7, hdr.getLangs().get(2).getValue(), 0.001);
|
||||
Assertions.assertEquals(1, hdr.getLangs().get(2).getOrder());
|
||||
Assertions.assertEquals("de", hdr.getLangs().get(2).getLang());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBadSyntax() {
|
||||
AcceptLanguageHeader hdr = new AcceptLanguageHeader("en; 0.9, ; q=0.7, *:0.1", true);
|
||||
Assertions.assertEquals(3, hdr.getLangs().size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -142,19 +142,22 @@ public class TerminologyServiceTests {
|
|||
engine.getContext().setExpansionProfile((org.hl7.fhir.r5.model.Parameters) loadResource("parameters-default.json"));
|
||||
}
|
||||
if (setup.test.asString("operation").equals("expand")) {
|
||||
expand(engine, req, resp, fp, ext);
|
||||
expand(engine, req, resp, setup.test.asString("Accept-Language"), fp, ext);
|
||||
} else if (setup.test.asString("operation").equals("validate-code")) {
|
||||
validate(engine, setup.test.asString("name"), req, resp, fp, ext);
|
||||
validate(engine, setup.test.asString("name"), req, resp, setup.test.asString("Accept-Language"), fp, ext);
|
||||
} else {
|
||||
Assertions.fail("Unknown Operation "+setup.test.asString("operation"));
|
||||
}
|
||||
}
|
||||
|
||||
private void expand(ValidationEngine engine, Resource req, String resp, String fp, JsonObject ext) throws IOException {
|
||||
private void expand(ValidationEngine engine, Resource req, String resp, String lang, String fp, JsonObject ext) throws IOException {
|
||||
org.hl7.fhir.r5.model.Parameters p = ( org.hl7.fhir.r5.model.Parameters) req;
|
||||
ValueSet vs = engine.getContext().fetchResource(ValueSet.class, p.getParameterValue("url").primitiveValue());
|
||||
boolean hierarchical = p.hasParameter("excludeNested") ? p.getParameterBool("excludeNested") == false : true;
|
||||
Assertions.assertNotNull(vs);
|
||||
if (lang != null && !p.hasParameter("displayLanguage")) {
|
||||
p.addParameter("displayLanguage", new CodeType(lang));
|
||||
}
|
||||
ValueSetExpansionOutcome vse = engine.getContext().expandVS(vs, false, hierarchical, false, p);
|
||||
if (resp.contains("\"ValueSet\"")) {
|
||||
if (vse.getValueset() == null) {
|
||||
|
@ -230,7 +233,7 @@ public class TerminologyServiceTests {
|
|||
}
|
||||
}
|
||||
|
||||
private void validate(ValidationEngine engine, String name, Resource req, String resp, String fp, JsonObject ext) throws JsonSyntaxException, FileNotFoundException, IOException {
|
||||
private void validate(ValidationEngine engine, String name, Resource req, String resp, String lang, String fp, JsonObject ext) throws JsonSyntaxException, FileNotFoundException, IOException {
|
||||
org.hl7.fhir.r5.model.Parameters p = (org.hl7.fhir.r5.model.Parameters) req;
|
||||
ValueSet vs = null;
|
||||
if (p.hasParameter("valueSetVersion")) {
|
||||
|
@ -241,6 +244,8 @@ public class TerminologyServiceTests {
|
|||
ValidationOptions options = new ValidationOptions();
|
||||
if (p.hasParameter("displayLanguage")) {
|
||||
options = options.withLanguage(p.getParameterString("displayLanguage"));
|
||||
} else if (lang != null ) {
|
||||
options = options.withLanguage(lang);
|
||||
}
|
||||
if (p.hasParameter("valueSetMode") && "CHECK_MEMBERSHIP_ONLY".equals(p.getParameterString("valueSetMode"))) {
|
||||
options = options.withCheckValueSetOnly();
|
||||
|
|
|
@ -214,15 +214,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
|
|||
if (!VersionUtilities.isR5Plus(val.getContext().getVersion())) {
|
||||
val.getBaseOptions().setUseValueSetDisplays(true);
|
||||
}
|
||||
val.getBaseOptions().getLanguages().clear();
|
||||
if (content.has("languages")) {
|
||||
for (String s : content.get("languages").getAsString().split("\\,")) {
|
||||
String l = s.trim();
|
||||
val.getBaseOptions().getLanguages().add(l);
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
val.getBaseOptions().setLanguages(content.get("languages").getAsString());
|
||||
|
||||
if (content.has("fetcher") && "standalone".equals(JsonUtilities.str(content, "fetcher"))) {
|
||||
val.setFetcher(vCurr);
|
||||
|
|
Loading…
Reference in New Issue