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