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.Base; 013import org.hl7.fhir.r5.model.CodeSystem; 014import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 015import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 016import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 017import org.hl7.fhir.r5.model.ContactDetail; 018import org.hl7.fhir.r5.model.DataType; 019import org.hl7.fhir.r5.model.ElementDefinition; 020import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; 021import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 022import org.hl7.fhir.r5.model.Extension; 023import org.hl7.fhir.r5.model.MarkdownType; 024import org.hl7.fhir.r5.model.PrimitiveType; 025import org.hl7.fhir.r5.model.Property; 026import org.hl7.fhir.r5.model.Resource; 027import org.hl7.fhir.r5.model.StringType; 028import org.hl7.fhir.r5.model.StructureDefinition; 029import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 030import org.hl7.fhir.r5.utils.ToolingExtensions; 031import org.hl7.fhir.utilities.TextFile; 032import org.hl7.fhir.utilities.Utilities; 033import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader; 034import org.hl7.fhir.utilities.i18n.AcceptLanguageHeader.LanguagePreference; 035import org.hl7.fhir.utilities.i18n.LanguageFileProducer; 036import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession; 037import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit; 038import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TranslationUnit; 039import org.hl7.fhir.utilities.json.model.JsonElement; 040import org.hl7.fhir.utilities.validation.ValidationMessage; 041import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 042import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 043import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 044 045/** 046 * in here: 047 * * generateTranslations 048 * * importFromTranslations 049 * * stripTranslations 050 * * switchLanguage 051 * 052 * in the validator 053 * 054 * @author grahamegrieve 055 * generateTranslations = -langTransform export -src {src} -tgt {tgt} -dest {dest} 056 * importFromTranslations = -langTransform import -src {src} -tgt {tgt} -dest {dest} 057 */ 058public class LanguageUtils { 059 060 public static final List<String> TRANSLATION_SUPPLEMENT_RESOURCE_TYPES = Arrays.asList("CodeSystem", "StructureDefinition", "Questionnaire"); 061 062 private static final String ORPHAN_TRANSLATIONS_NAME = "translations.orphans"; 063 064 private static final String SUPPLEMENT_SOURCE_RESOURCE = "translations.supplemented"; 065 private static final String SUPPLEMENT_SOURCE_TRANSLATIONS = "translations.source-list"; 066 067 IWorkerContext context; 068 private List<String> crlist; 069 070 071 public LanguageUtils(IWorkerContext context) { 072 super(); 073 this.context = context; 074 } 075 076 public void generateTranslations(Element resource, LanguageProducerLanguageSession session) { 077 translate(null, resource, session); 078 } 079 080 081 private void translate(Element parent, Element element, LanguageProducerLanguageSession langSession) { 082 if (element.isPrimitive() && isTranslatable(element)) { 083 String base = element.primitiveValue(); 084 if (base != null) { 085 String translation = getSpecialTranslation(parent, element, langSession.getTargetLang()); 086 if (translation == null) { 087 translation = element.getTranslation(langSession.getTargetLang()); 088 } 089 langSession.entry(new TextUnit(pathForElement(element), contextForElement(element), base, translation)); 090 } 091 } 092 for (Element c: element.getChildren()) { 093 if (!c.getName().equals("designation")) { 094 translate(element, c, langSession); 095 } 096 } 097 } 098 099 private String contextForElement(Element element) { 100 throw new Error("Not done yet"); 101 } 102 103 private String getSpecialTranslation(Element parent, Element element, String targetLang) { 104 if (parent == null) { 105 return null; 106 } 107 if (Utilities.existsInList(pathForElement(parent), "CodeSystem.concept", "CodeSystem.concept.concept") && "CodeSystem.concept.display".equals(pathForElement(element))) { 108 return getDesignationTranslation(parent, targetLang); 109 } 110 if (Utilities.existsInList(pathForElement(parent), "ValueSet.compose.include.concept") && "ValueSet.compose.include.concept.display".equals(pathForElement(element))) { 111 return getDesignationTranslation(parent, targetLang); 112 } 113 if (Utilities.existsInList(pathForElement(parent), "ValueSet.expansion.contains", "ValueSet.expansion.contains.contains") && "ValueSet.expansion.contains.display".equals(pathForElement(element))) { 114 return getDesignationTranslation(parent, targetLang); 115 } 116 return null; 117 } 118 119 private String getDesignationTranslation(Element parent, String targetLang) { 120 for (Element e : parent.getChildren("designation")) { 121 String lang = e.getNamedChildValue("language"); 122 if (langsMatch(targetLang, lang)) { 123 return e.getNamedChildValue("value"); 124 } 125 } 126 return null; 127 } 128 129 private boolean isTranslatable(Element element) { 130 return element.getProperty().isTranslatable(); 131 } 132 133 private String pathForElement(Element element) { 134 String bp = element.getBasePath(); 135 return pathForElement(bp, element.getProperty().getStructure().getType()); 136 } 137 138 private String pathForElement(String path, String type) { 139 // special case support for metadata elements prior to R5: 140 if (crlist == null) { 141 crlist = new ContextUtilities(context).getCanonicalResourceNames(); 142 } 143 if (crlist.contains(type)) { 144 String fp = path.replace(type+".", "CanonicalResource."); 145 if (Utilities.existsInList(fp, 146 "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name", 147 "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date", 148 "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext", 149 "CanonicalResource.jurisdiction")) { 150 return fp; 151 } 152 } 153 return path; 154 } 155 156 157 public int importFromTranslations(Element resource, List<TranslationUnit> translations) { 158 return importFromTranslations(null, resource, translations, new HashSet<>()); 159 } 160 161 public int importFromTranslations(Element resource, List<TranslationUnit> translations, List<ValidationMessage> messages) { 162 Set<TranslationUnit> usedUnits = new HashSet<>(); 163 int r = 0; 164 if (resource.fhirType().equals("StructureDefinition")) { 165 r = importFromTranslationsForSD(null, resource, translations, usedUnits); 166 } else { 167 r = importFromTranslations(null, resource, translations, usedUnits); 168 } 169 for (TranslationUnit t : translations) { 170 if (!usedUnits.contains(t)) { 171 if (messages != null) { 172 messages.add(new ValidationMessage(Source.Publisher, IssueType.INFORMATIONAL, t.getId(), "Unused '"+t.getLanguage()+"' translation '"+t.getSrcText()+"' -> '"+t.getTgtText()+"'", IssueSeverity.INFORMATION)); 173 } 174 } 175 } 176 return r; 177 } 178 179 public int importFromTranslations(Resource resource, List<TranslationUnit> translations, List<ValidationMessage> messages) { 180 Set<TranslationUnit> usedUnits = new HashSet<>(); 181 int r = 0; 182 if (resource.fhirType().equals("StructureDefinition")) { 183 // todo... r = importFromTranslationsForSD(null, resource, translations, usedUnits); 184 } else { 185 r = importResourceFromTranslations(null, resource, translations, usedUnits, resource.fhirType()); 186 } 187 for (TranslationUnit t : translations) { 188 if (!usedUnits.contains(t)) { 189 if (messages != null) { 190 messages.add(new ValidationMessage(Source.Publisher, IssueType.INFORMATIONAL, t.getId(), "Unused '"+t.getLanguage()+"' translation '"+t.getSrcText()+"' -> '"+t.getTgtText()+"'", IssueSeverity.INFORMATION)); 191 } 192 } 193 } 194 return r; 195 } 196 197 198 /* 199 * */ 200 private int importFromTranslationsForSD(Object object, Element resource, List<TranslationUnit> translations, Set<TranslationUnit> usedUnits) { 201 int r = 0; 202 r = r + checkForTranslations(translations, usedUnits, resource, "name", "name"); 203 r = r + checkForTranslations(translations, usedUnits, resource, "title", "title"); 204 r = r + checkForTranslations(translations, usedUnits, resource, "publisher", "publisher"); 205 for (Element cd : resource.getChildrenByName("contact")) { 206 r = r + checkForTranslations(translations, usedUnits, cd, "contact.name", "name"); 207 } 208 r = r + checkForTranslations(translations, usedUnits, resource, "purpose", "purpose"); 209 r = r + checkForTranslations(translations, usedUnits, resource, "copyright", "copyright"); 210 Element diff = resource.getNamedChild("differential"); 211 if (diff != null) { 212 for (Element ed : diff.getChildrenByName("element")) { 213 String id = ed.getNamedChildValue("id"); 214 r = r + checkForTranslations(translations, usedUnits, ed, id+"/label", "label"); 215 r = r + checkForTranslations(translations, usedUnits, ed, id+"/short", "short"); 216 r = r + checkForTranslations(translations, usedUnits, ed, id+"/definition", "definition"); 217 r = r + checkForTranslations(translations, usedUnits, ed, id+"/comment", "comment"); 218 r = r + checkForTranslations(translations, usedUnits, ed, id+"/requirements", "requirements"); 219 r = r + checkForTranslations(translations, usedUnits, ed, id+"/meaningWhenMissing", "meaningWhenMissing"); 220 r = r + checkForTranslations(translations, usedUnits, ed, id+"/orderMeaning", "orderMeaning"); 221 // for (ElementDefinitionConstraintComponent con : ed.getConstraint()) { 222 // addToList(list, lang, con, ed.getId()+"/constraint", "human", con.getHumanElement()); 223 // } 224 // if (ed.hasBinding()) { 225 // addToList(list, lang, ed.getBinding(), ed.getId()+"/b/desc", "description", ed.getBinding().getDescriptionElement()); 226 // for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 227 // addToList(list, lang, ab, ed.getId()+"/ab/doco", "documentation", ab.getDocumentationElement()); 228 // addToList(list, lang, ab, ed.getId()+"/ab/short", "shortDoco", ab.getShortDocoElement()); 229 // } 230 // } 231 } 232 } 233 return r; 234 } 235 236 private int checkForTranslations(List<TranslationUnit> translations, Set<TranslationUnit> usedUnits, Element context, String tname, String pname) { 237 int r = 0; 238 Element child = context.getNamedChild(pname); 239 if (child != null) { 240 String v = child.primitiveValue(); 241 if (v != null) { 242 for (TranslationUnit tu : translations) { 243 if (tname.equals(tu.getId()) && v.equals(tu.getSrcText())) { 244 usedUnits.add(tu); 245 child.setTranslation(tu.getLanguage(), tu.getTgtText()); 246 r++; 247 } 248 } 249 } 250 } 251 return r; 252 } 253 254 private int importResourceFromTranslations(Base parent, Base element, List<TranslationUnit> translations, Set<TranslationUnit> usedUnits, String path) { 255 int t = 0; 256 if (element.isPrimitive() && isTranslatable(element, path) && element instanceof org.hl7.fhir.r5.model.Element) { 257 org.hl7.fhir.r5.model.Element e = (org.hl7.fhir.r5.model.Element) element; 258 String base = element.primitiveValue(); 259 if (base != null) { 260 String epath = pathForElement(path, element.fhirType()); 261 Set<TranslationUnit> tlist = findTranslations(epath, base, translations); 262 for (TranslationUnit translation : tlist) { 263 t++; 264 if (!handleAsSpecial(parent, element, translation)) { 265 ToolingExtensions.setLanguageTranslation(e, translation.getLanguage(), translation.getTgtText()); 266 usedUnits.add(translation); 267 } 268 } 269 } 270 } 271 for (Property c : element.children()) { 272 for (Base v : c.getValues()) { 273 if (!c.getName().equals("designation")) { 274 t = t + importResourceFromTranslations(element, v, translations, usedUnits, path+"."+c.getName()); 275 } 276 } 277 } 278 return t; 279 } 280 281 private boolean handleAsSpecial(Base parent, Base element, TranslationUnit translation) { 282 return false; 283 } 284 285 private boolean isTranslatable(Base element, String path) { 286 return Utilities.existsInList(element.fhirType(), "string", "markdown"); 287 } 288 289 private int importFromTranslations(Element parent, Element element, List<TranslationUnit> translations, Set<TranslationUnit> usedUnits) { 290 int t = 0; 291 if (element.isPrimitive() && isTranslatable(element)) { 292 String base = element.primitiveValue(); 293 if (base != null) { 294 String path = pathForElement(element); 295 Set<TranslationUnit> tlist = findTranslations(path, base, translations); 296 for (TranslationUnit translation : tlist) { 297 t++; 298 if (!handleAsSpecial(parent, element, translation)) { 299 element.setTranslation(translation.getLanguage(), translation.getTgtText()); 300 usedUnits.add(translation); 301 } 302 } 303 } 304 } 305 // Create a copy of the children collection before iterating 306 List<Element> childrenCopy = List.copyOf(element.getChildren()); 307 for (Element c : childrenCopy) { 308 if (!c.getName().equals("designation")) { 309 t = t + importFromTranslations(element, c, translations, usedUnits); 310 } 311 } 312 return t; 313 } 314 315 private boolean handleAsSpecial(Element parent, Element element, TranslationUnit translation) { 316 if (parent == null) { 317 return false; 318 } 319 if (Utilities.existsInList(pathForElement(parent), "CodeSystem.concept", "CodeSystem.concept.concept") && "CodeSystem.concept.display".equals(pathForElement(element))) { 320 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 321 } 322 if (Utilities.existsInList(pathForElement(parent), "ValueSet.compose.include.concept") && "ValueSet.compose.include.concept.display".equals(pathForElement(element))) { 323 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 324 } 325 if (Utilities.existsInList(pathForElement(parent), "ValueSet.expansion.contains", "ValueSet.expansion.contains.contains") && "ValueSet.expansion.contains.display".equals(pathForElement(element))) { 326 return setDesignationTranslation(parent, translation.getLanguage(), translation.getTgtText()); 327 } 328 return false; 329 } 330 331 private boolean setDesignationTranslation(Element parent, String targetLang, String translation) { 332 for (Element e : parent.getChildren("designation")) { 333 String lang = e.getNamedChildValue("language"); 334 if (langsMatch(targetLang, lang)) { 335 Element value = e.getNamedChild("value"); 336 if (value != null) { 337 value.setValue(translation); 338 } else { 339 e.addElement("value").setValue(translation); 340 } 341 return true; 342 } 343 } 344 Element d = parent.addElement("designation"); 345 d.addElement("language").setValue(targetLang); 346 d.addElement("value").setValue(translation); 347 return true; 348 } 349 350 private Set<TranslationUnit> findTranslations(String path, String src, List<TranslationUnit> translations) { 351 Set<TranslationUnit> res = new HashSet<>(); 352 for (TranslationUnit translation : translations) { 353 if (path.equals(translation.getId()) && src.equals(translation.getSrcText())) { 354 res.add(translation); 355 } 356 } 357 return res; 358 } 359 360 public static boolean langsMatchExact(AcceptLanguageHeader langs, String srcLang) { 361 if (langs == null) { 362 return false; 363 } 364 for (LanguagePreference lang : langs.getLangs()) { 365 if (lang.getValue() > 0) { 366 if ("*".equals(lang.getLang())) { 367 return true; 368 } else { 369 return langsMatch(lang.getLang(), srcLang); 370 } 371 } 372 } 373 return false; 374 } 375 376 public static boolean langsMatch(AcceptLanguageHeader langs, String srcLang) { 377 if (langs == null) { 378 return false; 379 } 380 for (LanguagePreference lang : langs.getLangs()) { 381 if (lang.getValue() > 0) { 382 if ("*".equals(lang.getLang())) { 383 return true; 384 } else { 385 boolean ok = langsMatch(lang.getLang(), srcLang); 386 if (ok) { 387 return true; 388 } 389 } 390 } 391 } 392 return false; 393 } 394 395 public static boolean langsMatchExact(String dstLang, String srcLang) { 396 return dstLang == null ? false : dstLang.equals(srcLang); 397 } 398 399 public static boolean langsMatch(String dstLang, String srcLang) { 400 return dstLang == null || srcLang == null ? false : dstLang.startsWith(srcLang) || "*".equals(srcLang); 401 } 402 403 public void fillSupplement(CodeSystem csSrc, CodeSystem csDst, List<TranslationUnit> list) { 404 csDst.setUserData(SUPPLEMENT_SOURCE_RESOURCE, csSrc); 405 csDst.setUserData(SUPPLEMENT_SOURCE_TRANSLATIONS, list); 406 for (TranslationUnit tu : list) { 407 String code = tu.getId(); 408 String subCode = null; 409 if (code.contains("@")) { 410 subCode = code.substring(code.indexOf("@")+1); 411 code = code.substring(0, code.indexOf("@")); 412 } 413 ConceptDefinitionComponent cdSrc = CodeSystemUtilities.getCode(csSrc, tu.getId()); 414 if (cdSrc == null) { 415 addOrphanTranslation(csSrc, tu); 416 } else { 417 ConceptDefinitionComponent cdDst = CodeSystemUtilities.getCode(csDst, cdSrc.getCode()); 418 if (cdDst == null) { 419 cdDst = csDst.addConcept().setCode(cdSrc.getCode()); 420 } 421 String tt = tu.getTgtText(); 422 if (tt.startsWith("!!")) { 423 tt = tt.substring(3); 424 } 425 if (subCode == null) { 426 cdDst.setDisplay(tt); 427 } else if ("definition".equals(subCode)) { 428 cdDst.setDefinition(tt); 429 } else { 430 boolean found = false; 431 for (ConceptDefinitionDesignationComponent d : cdSrc.getDesignation()) { 432 if (d.hasUse() && subCode.equals(d.getUse().getCode())) { 433 found = true; 434 cdDst.addDesignation().setUse(d.getUse()).setLanguage(tu.getLanguage()).setValue(tt); //.setUserData(SUPPLEMENT_SOURCE, tu); 435 break; 436 } 437 } 438 if (!found) { 439 for (Extension e : cdSrc.getExtension()) { 440 if (subCode.equals(tail(e.getUrl()))) { 441 found = true; 442 cdDst.addExtension().setUrl(e.getUrl()).setValue( 443 e.getValue().fhirType().equals("markdown") ? new MarkdownType(tt) : new StringType(tt)); //.setUserData(SUPPLEMENT_SOURCE, tu); 444 break; 445 } 446 } 447 } 448 if (!found) { 449 addOrphanTranslation(csSrc, tu); 450 } 451 } 452 } 453 } 454 } 455 456 private String tail(String url) { 457 return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; 458 } 459 460 private void addOrphanTranslation(CodeSystem cs, TranslationUnit tu) { 461 List<TranslationUnit> list = (List<TranslationUnit>) cs.getUserData(ORPHAN_TRANSLATIONS_NAME); 462 if (list == null) { 463 list = new ArrayList<>(); 464 cs.setUserData(ORPHAN_TRANSLATIONS_NAME, list); 465 } 466 list.add(tu); 467 } 468 469 public String nameForLang(String lang) { 470 // todo: replace with structures from loading languages properly 471 switch (lang) { 472 case "en" : return "English"; 473 case "de" : return "German"; 474 case "es" : return "Spanish"; 475 case "nl" : return "Dutch"; 476 } 477 return Utilities.capitalize(lang); 478 } 479 480 public String titleForLang(String lang) { 481 // todo: replace with structures from loading languages properly 482 switch (lang) { 483 case "en" : return "English"; 484 case "de" : return "German"; 485 case "es" : return "Spanish"; 486 case "nl" : return "Dutch"; 487 } 488 return Utilities.capitalize(lang); 489 } 490 491 public boolean handlesAsResource(Resource resource) { 492 return (resource instanceof CodeSystem && resource.hasUserData(SUPPLEMENT_SOURCE_RESOURCE)) || (resource instanceof StructureDefinition); 493 } 494 495 public boolean handlesAsElement(Element element) { 496 return true; // for now... 497 } 498 499 public List<TranslationUnit> generateTranslations(Resource res, String lang) { 500 List<TranslationUnit> list = new ArrayList<>(); 501 if (res instanceof StructureDefinition) { 502 StructureDefinition sd = (StructureDefinition) res; 503 generateTranslations(list, sd, lang); 504 if (res.hasUserData(ORPHAN_TRANSLATIONS_NAME)) { 505 List<TranslationUnit> orphans = (List<TranslationUnit>) res.getUserData(ORPHAN_TRANSLATIONS_NAME); 506 for (TranslationUnit t : orphans) { 507 list.add(new TranslationUnit(lang, "!!"+t.getId(), t.getContext1(), t.getSrcText(), t.getTgtText())); 508 } 509 } 510 } else { 511 CodeSystem cs = (CodeSystem) res.getUserData(SUPPLEMENT_SOURCE_RESOURCE); 512 List<TranslationUnit> inputs = res.hasUserData(SUPPLEMENT_SOURCE_TRANSLATIONS) ? (List<TranslationUnit>) res.getUserData(SUPPLEMENT_SOURCE_TRANSLATIONS) : new ArrayList<>(); 513 for (ConceptDefinitionComponent cd : cs.getConcept()) { 514 generateTranslations(list, cd, lang, inputs); 515 } 516 if (cs.hasUserData(ORPHAN_TRANSLATIONS_NAME)) { 517 List<TranslationUnit> orphans = (List<TranslationUnit>) cs.getUserData(ORPHAN_TRANSLATIONS_NAME); 518 for (TranslationUnit t : orphans) { 519 list.add(new TranslationUnit(lang, "!!"+t.getId(), t.getContext1(), t.getSrcText(), t.getTgtText())); 520 } 521 } 522 } 523 return list; 524 } 525 526 private void generateTranslations(List<TranslationUnit> list, StructureDefinition sd, String lang) { 527 addToList(list, lang, sd, "name", "name", sd.getNameElement()); 528 addToList(list, lang, sd, "title", "title", sd.getTitleElement()); 529 addToList(list, lang, sd, "publisher", "publisher", sd.getPublisherElement()); 530 for (ContactDetail cd : sd.getContact()) { 531 addToList(list, lang, cd, "contact.name", "name", cd.getNameElement()); 532 } 533 addToList(list, lang, sd, "purpose", "purpose", sd.getPurposeElement()); 534 addToList(list, lang, sd, "copyright", "copyright", sd.getCopyrightElement()); 535 for (ElementDefinition ed : sd.getDifferential().getElement()) { 536 addToList(list, lang, ed, ed.getId()+"/label", "label", ed.getLabelElement()); 537 addToList(list, lang, ed, ed.getId()+"/short", "short", ed.getShortElement()); 538 addToList(list, lang, ed, ed.getId()+"/definition", "definition", ed.getDefinitionElement()); 539 addToList(list, lang, ed, ed.getId()+"/comment", "comment", ed.getCommentElement()); 540 addToList(list, lang, ed, ed.getId()+"/requirements", "requirements", ed.getRequirementsElement()); 541 addToList(list, lang, ed, ed.getId()+"/meaningWhenMissing", "meaningWhenMissing", ed.getMeaningWhenMissingElement()); 542 addToList(list, lang, ed, ed.getId()+"/orderMeaning", "orderMeaning", ed.getOrderMeaningElement()); 543 for (ElementDefinitionConstraintComponent con : ed.getConstraint()) { 544 addToList(list, lang, con, ed.getId()+"/constraint", "human", con.getHumanElement()); 545 } 546 if (ed.hasBinding()) { 547 addToList(list, lang, ed.getBinding(), ed.getId()+"/b/desc", "description", ed.getBinding().getDescriptionElement()); 548 for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) { 549 addToList(list, lang, ab, ed.getId()+"/ab/doco", "documentation", ab.getDocumentationElement()); 550 addToList(list, lang, ab, ed.getId()+"/ab/short", "shortDoco", ab.getShortDocoElement()); 551 } 552 } 553 } 554 } 555 556 private void addToList(List<TranslationUnit> list, String lang, Base ctxt, String name, String propName, DataType value) { 557 if (value != null && value.hasPrimitiveValue()) { 558 list.add(new TranslationUnit(lang, name, ctxt.getNamedProperty(propName).getDefinition(), value.primitiveValue(), value.getTranslation(lang))); 559 } 560 561 } 562 563 private void generateTranslations(List<TranslationUnit> list, ConceptDefinitionComponent cd, String lang, List<TranslationUnit> inputs) { 564 // we generate translation units for the display, the definition, and any designations and extensions that we find 565 // the id of the designation is the use.code (there will be a use) and for the extension, the tail of the extension URL 566 // todo: do we need to worry about name clashes? why would we, and more importantly, how would we solve that? 567 568 addTranslationUnit(list, cd.getCode(), cd.getDisplay(), lang, inputs); 569 if (cd.hasDefinition()) { 570 addTranslationUnit(list, cd.getCode()+"@definition", cd.getDefinition(), lang, inputs); 571 } 572 for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) { 573 addTranslationUnit(list, cd.getCode()+"@"+d.getUse().getCode(), d.getValue(), lang, inputs); 574 } 575 for (Extension e : cd.getExtension()) { 576 addTranslationUnit(list, cd.getCode()+"@"+tail(e.getUrl()), e.getValue().primitiveValue(), lang, inputs); 577 } 578 } 579 580 private void addTranslationUnit(List<TranslationUnit> list, String id, String srcText, String lang, List<TranslationUnit> inputs) { 581 TranslationUnit existing = null; 582 for (TranslationUnit t : inputs) { 583 if (id.equals(t.getId())) { 584 existing = t; 585 break; 586 } 587 } 588 // not sure what to do with context? 589 if (existing == null) { 590 list.add(new TranslationUnit(lang, id, null, srcText, null)); 591 } else if (srcText.equals(existing.getSrcText())) { 592 list.add(new TranslationUnit(lang, id, null, srcText, existing.getTgtText())); 593 } else { 594 list.add(new TranslationUnit(lang, id, null, srcText, "!!"+existing.getTgtText()).setOriginal(existing.getSrcText())); 595 } 596 } 597 598 private String getDefinition(ConceptDefinitionComponent cd) { 599 ConceptPropertyComponent v = CodeSystemUtilities.getProperty(cd, "translation-context"); 600 if (v != null && v.hasValue()) { 601 return v.getValue().primitiveValue(); 602 } else { 603 return cd.getDefinition(); 604 } 605 } 606 607 public List<TranslationUnit> generateTranslations(Element e, String lang) { 608 List<TranslationUnit> list = new ArrayList<>(); 609 generateTranslations(e, lang, list); 610 return list; 611 } 612 613 private void generateTranslations(Element e, String lang, List<TranslationUnit> list) { 614 if (e.getProperty().isTranslatable()) { 615 String id = pathForElement(e); // .getProperty().getDefinition().getPath(); 616 String context = e.getProperty().getDefinition().getDefinition(); 617 String src = e.primitiveValue(); 618 String tgt = getTranslation(e, lang); 619 list.add(new TranslationUnit(lang, id, context, src, tgt)); 620 } 621 if (e.hasChildren()) { 622 for (Element c : e.getChildren()) { 623 generateTranslations(c, lang, list); 624 } 625 } 626 627 } 628 629 private String getTranslation(Element e, String lang) { 630 if (!e.hasChildren()) { 631 return null; 632 } 633 for (Element ext : e.getChildren()) { 634 if ("Extension".equals(ext.fhirType()) && "http://hl7.org/fhir/StructureDefinition/translation".equals(ext.getNamedChildValue("url"))) { 635 String l = null; 636 String v = null; 637 for (Element subExt : ext.getChildren()) { 638 if ("Extension".equals(subExt.fhirType()) && "lang".equals(subExt.getNamedChildValue("url"))) { 639 l = subExt.getNamedChildValue("value"); 640 } 641 if ("Extension".equals(subExt.fhirType()) && "content".equals(subExt.getNamedChildValue("url"))) { 642 v = subExt.getNamedChildValue("value"); 643 } 644 } 645 if (lang.equals(l)) { 646 return v; 647 } 648 } 649 } 650 return null; 651 } 652 653 public boolean switchLanguage(Element e, String lang) { 654 if (e.getProperty().isTranslatable()) { 655 String cnt = getTranslation(e, lang); 656 e.removeExtension(ToolingExtensions.EXT_TRANSLATION); 657 if (cnt != null) { 658 e.setValue(cnt); 659 } 660 } 661 if (e.hasChildren()) { 662 for (Element c : e.getChildren()) { 663 if (!switchLanguage(c, lang)) { 664 return false; 665 } 666 } 667 } 668 return true; 669 } 670 671 public boolean hasTranslation(org.hl7.fhir.r5.model.Element e, String lang) { 672 return getTranslation(e, lang) != null; 673 } 674 675 public String getTranslation(org.hl7.fhir.r5.model.Element e, String lang) { 676 for (Extension ext : e.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 677 String l = ext.getExtensionString("lang"); 678 String v = ext.getExtensionString("content"); 679 if (langsMatch(l, lang) && v != null) { 680 return v; 681 } 682 } 683 return null; 684 } 685 686 public String getTranslationOrBase(PrimitiveType<?> e, String lang) { 687 for (Extension ext : e.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATION)) { 688 String l = ext.getExtensionString("lang"); 689 String v = ext.getExtensionString("content"); 690 if (langsMatch(l, lang) && v != null) { 691 return v; 692 } 693 } 694 return e.primitiveValue(); 695 } 696}