001package org.hl7.fhir.r5.elementmodel; 002 003import java.util.ArrayList; 004import java.util.Arrays; 005import java.util.HashSet; 006import java.util.List; 007import java.util.Set; 008 009import org.checkerframework.checker.units.qual.cd; 010import org.hl7.fhir.r5.context.ContextUtilities; 011import org.hl7.fhir.r5.context.IWorkerContext; 012import org.hl7.fhir.r5.model.CodeSystem; 013import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 014import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 015import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 016import org.hl7.fhir.r5.model.DataType; 017import org.hl7.fhir.r5.model.Resource; 018import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 019import org.hl7.fhir.utilities.TextFile; 020import org.hl7.fhir.utilities.Utilities; 021import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader; 022import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference; 023import org.hl7.fhir.utilities.i18n.LanguageFileProducer; 024import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession; 025import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit; 026import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TranslationUnit; 027 028/** 029 * in here: 030 * * generateTranslations 031 * * importFromTranslations 032 * * stripTranslations 033 * * switchLanguage 034 * 035 * in the validator 036 * 037 * @author grahamegrieve 038 * generateTranslations = -langTransform export -src {src} -tgt {tgt} -dest {dest} 039 * importFromTranslations = -langTransform import -src {src} -tgt {tgt} -dest {dest} 040 */ 041public class LanguageUtils { 042 043 public static final List<String> TRANSLATION_SUPPLEMENT_RESOURCE_TYPES = Arrays.asList("CodeSystem", "StructureDefinition", "Questionnaire"); 044 045 private static final String ORPHAN_TRANSLATIONS_NAME = "translations.orphans"; 046 047 private static final String SUPPLEMENT_NAME = "translations.supplement"; 048 049 IWorkerContext context; 050 private List<String> crlist; 051 052 053 public LanguageUtils(IWorkerContext context) { 054 super(); 055 this.context = context; 056 } 057 058 public void generateTranslations(Element resource, LanguageProducerLanguageSession session) { 059 translate(null, resource, session); 060 } 061 062 063 private void translate(Element parent, Element element, LanguageProducerLanguageSession langSession) { 064 if (element.isPrimitive() && isTranslatable(element)) { 065 String base = element.primitiveValue(); 066 if (base != null) { 067 String translation = getSpecialTranslation(parent, element, langSession.getTargetLang()); 068 if (translation == null) { 069 translation = element.getTranslation(langSession.getTargetLang()); 070 } 071 langSession.entry(new TextUnit(pathForElement(element), contextForElement(element), base, translation)); 072 } 073 } 074 for (Element c: element.getChildren()) { 075 if (!c.getName().equals("designation")) { 076 translate(element, c, langSession); 077 } 078 } 079 } 080 081 082 private String contextForElement(Element element) { 083 throw new Error("Not done yet"); 084 } 085 086 private String getSpecialTranslation(Element parent, Element element, String targetLang) { 087 if (parent == null) { 088 return null; 089 } 090 if (Utilities.existsInList(pathForElement(parent), "CodeSystem.concept", "CodeSystem.concept.concept") && "CodeSystem.concept.display".equals(pathForElement(element))) { 091 return getDesignationTranslation(parent, targetLang); 092 } 093 if (Utilities.existsInList(pathForElement(parent), "ValueSet.compose.include.concept") && "ValueSet.compose.include.concept.display".equals(pathForElement(element))) { 094 return getDesignationTranslation(parent, targetLang); 095 } 096 if (Utilities.existsInList(pathForElement(parent), "ValueSet.expansion.contains", "ValueSet.expansion.contains.contains") && "ValueSet.expansion.contains.display".equals(pathForElement(element))) { 097 return getDesignationTranslation(parent, targetLang); 098 } 099 return null; 100 } 101 102 private String getDesignationTranslation(Element parent, String targetLang) { 103 for (Element e : parent.getChildren("designation")) { 104 String lang = e.getNamedChildValue("language"); 105 if (langsMatch(targetLang, lang)) { 106 return e.getNamedChildValue("value"); 107 } 108 } 109 return null; 110 } 111 112 private boolean isTranslatable(Element element) { 113 return element.getProperty().isTranslatable() && !Utilities.existsInList(pathForElement(element), "CanonicalResource.version"); 114 } 115 116 private String pathForElement(Element element) { 117 // special case support for metadata elements prior to R5: 118 if (crlist == null) { 119 crlist = new ContextUtilities(context).getCanonicalResourceNames(); 120 } 121 String bp = element.getBasePath(); 122 if (crlist.contains(element.getProperty().getStructure().getType())) { 123 String fp = bp.replace(element.getProperty().getStructure().getType()+".", "CanonicalResource."); 124 if (Utilities.existsInList(fp, 125 "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 126 "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date", 127 "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 128 "CanonicalResource.jurisdiction")) { 129 return fp; 130 } 131 } 132 return bp; 133 } 134 135 public int importFromTranslations(Element resource, Set<TranslationUnit> translations) { 136 return importFromTranslations(null, resource, translations); 137 } 138 139 private int importFromTranslations(Element parent, Element element, Set<TranslationUnit> translations) { 140 int t = 0; 141 if (element.isPrimitive() && isTranslatable(element)) { 142 String base = element.primitiveValue(); 143 if (base != null) { 144 Set<TranslationUnit> tlist = findTranslations(pathForElement(element), base, translations); 145 for (TranslationUnit translation : tlist) { 146 t++; 147 if (!handleAsSpecial(parent, element, translation)) { 148 element.setTranslation(translation.getLanguage(), translation.getTgtText()); 149 } 150 } 151 } 152 } 153 for (Element c: element.getChildren()) { 154 if (!c.getName().equals("designation")) { 155 t = t + importFromTranslations(element, c, translations); 156 } 157 } 158 return t; 159 } 160 161 private boolean handleAsSpecial(Element parent, Element element, TranslationUnit translation) { 162 if (parent == null) { 163 return false; 164 } 165 if (Utilities.existsInList(pathForElement(parent), "CodeSystem.concept", "CodeSystem.concept.concept") && "CodeSystem.concept.display".equals(pathForElement(element))) { 166 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 167 } 168 if (Utilities.existsInList(pathForElement(parent), "ValueSet.compose.include.concept") && "ValueSet.compose.include.concept.display".equals(pathForElement(element))) { 169 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 170 } 171 if (Utilities.existsInList(pathForElement(parent), "ValueSet.expansion.contains", "ValueSet.expansion.contains.contains") && "ValueSet.expansion.contains.display".equals(pathForElement(element))) { 172 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 173 } 174 return false; 175 } 176 177 private boolean setDesignationTranslation(Element parent, String targetLang, String translation) { 178 for (Element e : parent.getChildren("designation")) { 179 String lang = e.getNamedChildValue("language"); 180 if (langsMatch(targetLang, lang)) { 181 Element value = e.getNamedChild("value"); 182 if (value != null) { 183 value.setValue(translation); 184 } else { 185 e.addElement("value").setValue(translation); 186 } 187 return true; 188 } 189 } 190 Element d = parent.addElement("designation"); 191 d.addElement("language").setValue(targetLang); 192 d.addElement("value").setValue(translation); 193 return true; 194 } 195 196 private Set<TranslationUnit> findTranslations(String path, String src, Set<TranslationUnit> translations) { 197 Set<TranslationUnit> res = new HashSet<>(); 198 for (TranslationUnit translation : translations) { 199 if (path.equals(translation.getId()) && src.equals(translation.getSrcText())) { 200 res.add(translation); 201 } 202 } 203 return res; 204 } 205 206 public static boolean langsMatchExact(AcceptLanguageHeader langs, String srcLang) { 207 if (langs == null) { 208 return false; 209 } 210 for (LanguagePreference lang : langs.getLangs()) { 211 if (lang.getValue() > 0) { 212 if ("*".equals(lang.getLang())) { 213 return true; 214 } else { 215 return langsMatch(lang.getLang(), srcLang); 216 } 217 } 218 } 219 return false; 220 } 221 222 public static boolean langsMatch(AcceptLanguageHeader langs, String srcLang) { 223 if (langs == null) { 224 return false; 225 } 226 for (LanguagePreference lang : langs.getLangs()) { 227 if (lang.getValue() > 0) { 228 if ("*".equals(lang.getLang())) { 229 return true; 230 } else { 231 boolean ok = langsMatch(lang.getLang(), srcLang); 232 if (ok) { 233 return true; 234 } 235 } 236 } 237 } 238 return false; 239 } 240 241 public static boolean langsMatchExact(String dstLang, String srcLang) { 242 return dstLang == null ? false : dstLang.equals(srcLang); 243 } 244 245 public static boolean langsMatch(String dstLang, String srcLang) { 246 return dstLang == null || srcLang == null ? false : dstLang.startsWith(srcLang) || "*".equals(srcLang); 247 } 248 249 public static void fillSupplement(CodeSystem cs, List<TranslationUnit> list) { 250 cs.setUserData(SUPPLEMENT_NAME, "true"); 251 for (TranslationUnit tu : list) { 252 ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(cs, tu.getId()); 253 if (cd != null && cd.hasDisplay() && cd.getDisplay().equals(tu.getSrcText())) { 254 cd.addDesignation().setLanguage(tu.getLanguage()).setValue(tu.getTgtText()); 255 } else { 256 addOrphanTranslation(cs, tu); 257 } 258 } 259 } 260 261 private static void addOrphanTranslation(CodeSystem cs, TranslationUnit tu) { 262 List<TranslationUnit> list = (List<TranslationUnit>) cs.getUserData(ORPHAN_TRANSLATIONS_NAME); 263 if (list == null) { 264 list = new ArrayList<>(); 265 cs.setUserData(ORPHAN_TRANSLATIONS_NAME, list); 266 } 267 list.add(tu); 268 } 269 270 public static String nameForLang(String lang) { 271 // todo: replace with structures from loading languages properly 272 switch (lang) { 273 case "en" : return "English"; 274 case "de" : return "German"; 275 case "es" : return "Spanish"; 276 case "nl" : return "Dutch"; 277 } 278 return Utilities.capitalize(lang); 279 } 280 281 public static String titleForLang(String lang) { 282 // todo: replace with structures from loading languages properly 283 switch (lang) { 284 case "en" : return "English"; 285 case "de" : return "German"; 286 case "es" : return "Spanish"; 287 case "nl" : return "Dutch"; 288 } 289 return Utilities.capitalize(lang); 290 } 291 292 public static boolean handlesAsResource(Resource resource) { 293 return (resource instanceof CodeSystem && resource.hasUserData(SUPPLEMENT_NAME)); 294 } 295 296 public static boolean handlesAsElement(Element element) { 297 return false; // for now... 298 } 299 300 public static List<TranslationUnit> generateTranslations(Resource res, String lang) { 301 List<TranslationUnit> list = new ArrayList<>(); 302 CodeSystem cs = (CodeSystem) res; 303 for (ConceptDefinitionComponent cd : cs.getConcept()) { 304 generateTranslations(list, cd, lang); 305 } 306 return list; 307 } 308 309 private static void generateTranslations(List<TranslationUnit> list, ConceptDefinitionComponent cd, String lang) { 310 String code = cd.getCode(); 311 String display = cd.getDisplay(); 312 String target = null; 313 for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) { 314 if (target == null && !d.hasUse() && d.hasLanguage() && lang.equals(d.getLanguage())) { 315 target = d.getValue(); 316 } 317 } 318 for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) { 319 if (target == null && d.hasLanguage() && lang.equals(d.getLanguage())) { 320 target = d.getValue(); 321 } 322 } 323 list.add(new TranslationUnit(lang, code, getDefinition(cd), display, target)); 324 for (ConceptDefinitionComponent cd1 : cd.getConcept()) { 325 generateTranslations(list, cd1, lang); 326 } 327 } 328 329 private static String getDefinition(ConceptDefinitionComponent cd) { 330 ConceptPropertyComponent v = CodeSystemUtilities.getProperty(cd, "translation-context"); 331 if (v != null && v.hasValue()) { 332 return v.getValue().primitiveValue(); 333 } else { 334 return cd.getDefinition(); 335 } 336 } 337}