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