001package org.hl7.fhir.r5.renderers; 
002 
003import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 
004import static java.time.temporal.ChronoField.YEAR; 
005 
006import java.io.IOException; 
007import java.math.BigDecimal; 
008import java.text.NumberFormat; 
009import java.time.LocalDate; 
010import java.time.ZoneId; 
011import java.time.ZonedDateTime; 
012import java.time.format.DateTimeFormatter; 
013import java.time.format.DateTimeFormatterBuilder; 
014import java.time.format.FormatStyle; 
015import java.time.format.SignStyle; 
016import java.util.Currency; 
017import java.util.List; 
018 
019import org.hl7.fhir.exceptions.DefinitionException; 
020import org.hl7.fhir.exceptions.FHIRException; 
021import org.hl7.fhir.exceptions.FHIRFormatError; 
022import org.hl7.fhir.r5.context.ContextUtilities; 
023import org.hl7.fhir.r5.context.IWorkerContext; 
024import org.hl7.fhir.r5.elementmodel.Element; 
025import org.hl7.fhir.r5.model.Address; 
026import org.hl7.fhir.r5.model.Annotation; 
027import org.hl7.fhir.r5.model.BackboneType; 
028import org.hl7.fhir.r5.model.Base; 
029import org.hl7.fhir.r5.model.Base64BinaryType; 
030import org.hl7.fhir.r5.model.BaseDateTimeType; 
031import org.hl7.fhir.r5.model.CanonicalResource; 
032import org.hl7.fhir.r5.model.CanonicalType; 
033import org.hl7.fhir.r5.model.CodeSystem; 
034import org.hl7.fhir.r5.model.CodeType; 
035import org.hl7.fhir.r5.model.CodeableConcept; 
036import org.hl7.fhir.r5.model.CodeableReference; 
037import org.hl7.fhir.r5.model.Coding; 
038import org.hl7.fhir.r5.model.ContactDetail; 
039import org.hl7.fhir.r5.model.ContactPoint; 
040import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 
041import org.hl7.fhir.r5.model.DataRequirement; 
042import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent; 
043import org.hl7.fhir.r5.model.DataRequirement.DataRequirementDateFilterComponent; 
044import org.hl7.fhir.r5.model.DataRequirement.DataRequirementSortComponent; 
045import org.hl7.fhir.r5.model.DataRequirement.SortDirection; 
046import org.hl7.fhir.r5.model.DataType; 
047import org.hl7.fhir.r5.model.DateTimeType; 
048import org.hl7.fhir.r5.model.DateType; 
049import org.hl7.fhir.r5.model.ElementDefinition; 
050import org.hl7.fhir.r5.model.Enumeration; 
051import org.hl7.fhir.r5.model.Expression; 
052import org.hl7.fhir.r5.model.Extension; 
053import org.hl7.fhir.r5.model.ExtensionHelper; 
054import org.hl7.fhir.r5.model.HumanName; 
055import org.hl7.fhir.r5.model.HumanName.NameUse; 
056import org.hl7.fhir.r5.model.IdType; 
057import org.hl7.fhir.r5.model.Identifier; 
058import org.hl7.fhir.r5.model.MarkdownType; 
059import org.hl7.fhir.r5.model.Money; 
060import org.hl7.fhir.r5.model.NamingSystem; 
061import org.hl7.fhir.r5.model.Period; 
062import org.hl7.fhir.r5.model.PrimitiveType; 
063import org.hl7.fhir.r5.model.Quantity; 
064import org.hl7.fhir.r5.model.Range; 
065import org.hl7.fhir.r5.model.Reference; 
066import org.hl7.fhir.r5.model.Resource; 
067import org.hl7.fhir.r5.model.SampledData; 
068import org.hl7.fhir.r5.model.StringType; 
069import org.hl7.fhir.r5.model.StructureDefinition; 
070import org.hl7.fhir.r5.model.Timing; 
071import org.hl7.fhir.r5.model.Timing.EventTiming; 
072import org.hl7.fhir.r5.model.Timing.TimingRepeatComponent; 
073import org.hl7.fhir.r5.model.Timing.UnitsOfTime; 
074import org.hl7.fhir.r5.model.TriggerDefinition; 
075import org.hl7.fhir.r5.model.UriType; 
076import org.hl7.fhir.r5.model.UsageContext; 
077import org.hl7.fhir.r5.model.ValueSet; 
078import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 
079import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 
080import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 
081import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; 
082import org.hl7.fhir.r5.renderers.utils.RenderingContext; 
083import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 
084import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 
085import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 
086import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 
087import org.hl7.fhir.r5.utils.ToolingExtensions; 
088import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 
089import org.hl7.fhir.utilities.Utilities; 
090import org.hl7.fhir.utilities.VersionUtilities; 
091import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 
092import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 
093import org.hl7.fhir.utilities.xhtml.NodeType; 
094import org.hl7.fhir.utilities.xhtml.XhtmlNode; 
095import org.hl7.fhir.utilities.xhtml.XhtmlParser; 
096 
097import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 
098 
099public class DataRenderer extends Renderer implements CodeResolver { 
100   
101  // -- 1. context -------------------------------------------------------------- 
102     
103  public DataRenderer(RenderingContext context) { 
104    super(context); 
105  } 
106 
107  public DataRenderer(IWorkerContext worker) { 
108    super(worker); 
109  } 
110 
111  // -- 2. Markdown support ------------------------------------------------------- 
112    
113  public static String processRelativeUrls(String markdown, String path) { 
114    if (markdown == null) { 
115      return ""; 
116    } 
117    if (!Utilities.isAbsoluteUrl(path)) { 
118      return markdown; 
119    } 
120    String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; 
121    StringBuilder b = new StringBuilder(); 
122    int i = 0; 
123    while (i < markdown.length()) { 
124      if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 
125        int j = i + 2; 
126        while (j < markdown.length() && markdown.charAt(j) != ')') 
127          j++; 
128        if (j < markdown.length()) { 
129          String url = markdown.substring(i+2, j); 
130          if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 
131            // it's relative - so it's relative to the base URL 
132              b.append("]("); 
133              b.append(basePath); 
134          } else { 
135            b.append("]("); 
136          } 
137          i = i + 1; 
138        } else  
139          b.append(markdown.charAt(i)); 
140      } else { 
141        b.append(markdown.charAt(i)); 
142      } 
143      i++; 
144    } 
145    return b.toString(); 
146  } 
147 
148  protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { 
149    addMarkdown(x, processRelativeUrls(text, path)); 
150  } 
151   
152  protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 
153    if (text != null) { 
154      // 1. custom FHIR extensions 
155      while (text.contains("[[[")) { 
156        String left = text.substring(0, text.indexOf("[[[")); 
157        String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 
158        String right = text.substring(text.indexOf("]]]")+3); 
159        String path = null; 
160        String url = link; 
161        String[] parts = link.split("\\#"); 
162        if (parts[0].contains(".")) { 
163          path = parts[0]; 
164          parts[0] = parts[0].substring(0, parts[0].indexOf(".")); 
165        } 
166        StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]); 
167        if (p == null) { 
168          p = getContext().getWorker().fetchTypeDefinition(parts[0]); 
169        } 
170        if (context.getTypeMap().containsKey(parts[0])) { 
171          p = getContext().getWorker().fetchTypeDefinition(context.getTypeMap().get(parts[0]));           
172        } 
173        if (p == null) { 
174          p = getContext().getWorker().fetchResource(StructureDefinition.class, link); 
175        } 
176        if (p != null) { 
177          if ("Extension".equals(p.getType())) { 
178            path = null; 
179          } else if (p.hasSnapshot()) { 
180            path = p.getSnapshot().getElementFirstRep().getPath(); 
181          } else if (Utilities.isAbsoluteUrl(path)) { 
182            path = null; 
183          } 
184          url = p.getWebPath(); 
185          if (url == null) { 
186            url = p.getUserString("filename"); 
187          } 
188        } else { 
189          throw new DefinitionException(context.formatPhrase(RenderingContext.DATA_REND_MKDWN_LNK, link) + " "); 
190        } 
191         
192        text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right; 
193      } 
194   
195      // 2. markdown 
196      String s = getContext().getMarkdown().process(text, "narrative generator"); 
197      XhtmlParser p = new XhtmlParser(); 
198      XhtmlNode m; 
199      try { 
200        m = p.parse("<div>"+s+"</div>", "div"); 
201      } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 
202        throw new FHIRFormatError(e.getMessage(), e); 
203      } 
204      x.getChildNodes().addAll(m.getChildNodes()); 
205    } 
206  } 
207 
208  protected void smartAddText(XhtmlNode p, String text) { 
209    if (text == null) 
210      return; 
211   
212    String[] lines = text.split("\\r\\n"); 
213    for (int i = 0; i < lines.length; i++) { 
214      if (i > 0) 
215        p.br(); 
216      p.addText(lines[i]); 
217    } 
218  } 
219  
220  // -- 3. General Purpose Terminology Support ----------------------------------------- 
221 
222  private static String snMonth(String m) { 
223    switch (m) { 
224    case "1" : return "Jan"; 
225    case "2" : return "Feb"; 
226    case "3" : return "Mar"; 
227    case "4" : return "Apr"; 
228    case "5" : return "May"; 
229    case "6" : return "Jun"; 
230    case "7" : return "Jul"; 
231    case "8" : return "Aug"; 
232    case "9" : return "Sep"; 
233    case "10" : return "Oct"; 
234    case "11" : return "Nov"; 
235    case "12" : return "Dec"; 
236    default: return null; 
237    } 
238  } 
239   
240  public static String describeVersion(String version) { 
241    if (version.startsWith("http://snomed.info/sct")) { 
242      String[] p = version.split("\\/"); 
243      String ed = null; 
244      String dt = ""; 
245 
246      if (p[p.length-2].equals("version")) { 
247        ed = p[p.length-3]; 
248        String y = p[p.length-3].substring(4, 8); 
249        String m = p[p.length-3].substring(2, 4);  
250        dt = " rel. "+snMonth(m)+" "+y; 
251      } else { 
252        ed = p[p.length-1]; 
253      } 
254      switch (ed) { 
255      case "900000000000207008": return "Intl"+dt;  
256      case "731000124108": return "US"+dt;  
257      case "32506021000036107": return "AU"+dt;  
258      case "449081005": return "ES"+dt;  
259      case "554471000005108": return "DK"+dt;  
260      case "11000146104": return "NL"+dt;  
261      case "45991000052106": return "SE"+dt;  
262      case "999000041000000102": return "UK"+dt;  
263      case "20611000087101": return "CA"+dt;  
264      case "11000172109": return "BE"+dt;  
265      default: return "??"+dt;  
266      }       
267    } else { 
268      return version; 
269    } 
270  } 
271   
272  public String displaySystem(String system) { 
273    if (system == null) 
274        return (context.formatPhrase(RenderingContext.DATA_REND_NOT_STAT)); 
275    if (system.equals("http://loinc.org")) 
276        return (context.formatPhrase(RenderingContext.DATA_REND_LOINC)); 
277    if (system.startsWith("http://snomed.info")) 
278         return (context.formatPhrase(RenderingContext.DATA_REND_SNOMED)); 
279    if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 
280        return (context.formatPhrase(RenderingContext.DATA_REND_RXNORM)); 
281    if (system.equals("http://hl7.org/fhir/sid/icd-9")) 
282        return (context.formatPhrase(RenderingContext.DATA_REND_ICD)); 
283    if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 
284        return (context.formatPhrase(RenderingContext.DATA_REND_DICOM)); 
285    if (system.equals("http://unitsofmeasure.org")) 
286        return (context.formatPhrase(RenderingContext.GENERAL_UCUM)); 
287 
288    CodeSystem cs = context.getContext().fetchCodeSystem(system); 
289    if (cs != null) { 
290      return crPresent(cs); 
291    } 
292    return tails(system); 
293  } 
294 
295  private String crPresent(CanonicalResource cr) { 
296    if (cr.hasUserData("presentation")) { 
297      return cr.getUserString("presentation"); 
298    } 
299    if (cr.hasTitle()) 
300      return context.getTranslated(cr.getTitleElement()); 
301    if (cr.hasName()) 
302      return context.getTranslated(cr.getNameElement()); 
303    return cr.toString(); 
304  } 
305 
306  private String tails(String system) { 
307    if (system.contains("/")) { 
308      return system.substring(system.lastIndexOf("/")+1); 
309    } else { 
310      return (context.formatPhrase(RenderingContext.DATA_REND_UNKNWN)); 
311    } 
312  } 
313 
314  protected String makeAnchor(String codeSystem, String code) { 
315    String s = codeSystem+'-'+code; 
316    StringBuilder b = new StringBuilder(); 
317    for (char c : s.toCharArray()) { 
318      if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.') 
319        b.append(c); 
320      else 
321        b.append('-'); 
322    } 
323    return b.toString(); 
324  } 
325 
326  private String lookupCode(String system, String version, String code) { 
327    if (JurisdictionUtilities.isJurisdiction(system)) { 
328      return JurisdictionUtilities.displayJurisdiction(system+"#"+code); 
329    } 
330    ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withLanguage(context.getLocale().toString().replace("_", "-")).withVersionFlexible(true), system, version, code, null); 
331 
332    if (t != null && t.getDisplay() != null) 
333      return t.getDisplay(); 
334    else 
335      return code; 
336  } 
337 
338  protected String describeLang(String lang) { 
339    // special cases: 
340    if ("fr-CA".equals(lang)) { 
341      return "French (Canadian)"; // this one was omitted from the value set 
342    } 
343    ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 
344    if (v != null) { 
345      ConceptReferenceComponent l = null; 
346      for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
347        if (cc.getCode().equals(lang)) 
348          l = cc; 
349      } 
350      if (l == null) { 
351        if (lang.contains("-")) { 
352          lang = lang.substring(0, lang.indexOf("-")); 
353        } 
354        for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
355          if (cc.getCode().equals(lang)) { 
356            l = cc; 
357            break; 
358          } 
359        } 
360        if (l == null) { 
361          for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 
362            if (cc.getCode().startsWith(lang+"-")) { 
363              l = cc; 
364              break; 
365            } 
366          } 
367        } 
368      } 
369      if (l != null) { 
370        if (lang.contains("-")) 
371          lang = lang.substring(0, lang.indexOf("-")); 
372        String en = l.getDisplay(); 
373        String nativelang = null; 
374        for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 
375          if (cd.getLanguage().equals(lang)) 
376            nativelang = cd.getValue(); 
377        } 
378        if (nativelang == null) 
379          return en+" ("+lang+")"; 
380        else 
381          return nativelang+" ("+en+", "+lang+")"; 
382      } 
383    } 
384    return lang; 
385  } 
386 
387  private boolean isCanonical(String path) { 
388    if (!path.endsWith(".url"))  
389      return false; 
390    String t = path.substring(0, path.length()-4); 
391    StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t); 
392    if (sd == null) 
393      return false; 
394    if (VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()).contains(t)) { 
395      return true; 
396    } 
397    if (Utilities.existsInList(t,  
398        "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", 
399        "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", 
400        "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" 
401        )) 
402      return true; 
403    return false; 
404  } 
405 
406  // -- 4. Language support ------------------------------------------------------ 
407   
408  public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { 
409    return context.getTranslated(value); 
410  } 
411   
412  // -- 6. General purpose extension rendering ----------------------------------------------  
413 
414  public boolean hasRenderableExtensions(DataType element) { 
415    for (Extension ext : element.getExtension()) { 
416      if (canRender(ext)) { 
417        return true; 
418      } 
419    } 
420    return false; 
421  } 
422   
423  public boolean hasRenderableExtensions(BackboneType element) { 
424    for (Extension ext : element.getExtension()) { 
425      if (canRender(ext)) { 
426        return true; 
427      } 
428    } 
429    return element.hasModifierExtension();   
430  } 
431   
432  private String getExtensionLabel(Extension ext) { 
433    StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.getUrl()); 
434    if (sd != null && ext.hasValue() && ext.getValue().isPrimitive() && sd.hasSnapshot()) { 
435      for (ElementDefinition ed : sd.getSnapshot().getElement()) { 
436        if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 
437          return context.getTranslated(ed.getLabelElement()); 
438        } 
439      } 
440    } 
441    return null;     
442  } 
443   
444  private boolean canRender(Extension ext) { 
445    return getExtensionLabel(ext) != null; 
446  } 
447 
448  public void renderExtensionsInList(XhtmlNode ul, DataType element) throws FHIRFormatError, DefinitionException, IOException { 
449    for (Extension ext : element.getExtension()) { 
450      if (canRender(ext)) { 
451        String lbl = getExtensionLabel(ext); 
452        XhtmlNode li = ul.li(); 
453        li.tx(lbl); 
454        li.tx(": "); 
455        render(li, ext.getValue()); 
456      } 
457    } 
458  } 
459   
460  public void renderExtensionsInList(XhtmlNode ul, BackboneType element) throws FHIRFormatError, DefinitionException, IOException { 
461    for (Extension ext : element.getModifierExtension()) { 
462      if (canRender(ext)) { 
463        String lbl = getExtensionLabel(ext); 
464        XhtmlNode li = ul.li(); 
465        li = li.b(); 
466        li.tx(lbl); 
467        li.tx(": ");         
468        render(li, ext.getValue()); 
469      } else { 
470        // somehow have to do better than this  
471        XhtmlNode li = ul.li(); 
472        li.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 
473      } 
474    } 
475    for (Extension ext : element.getExtension()) { 
476      if (canRender(ext)) { 
477        String lbl = getExtensionLabel(ext); 
478        XhtmlNode li = ul.li(); 
479        li.tx(lbl); 
480        li.tx(": "); 
481        render(li, ext.getValue()); 
482      } 
483    } 
484  } 
485   
486  public void renderExtensionsInText(XhtmlNode div, DataType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 
487    boolean first = true; 
488    for (Extension ext : element.getExtension()) { 
489      if (canRender(ext)) { 
490        if (first) { 
491          first = false; 
492        } else { 
493          div.tx(sep); 
494          div.tx(" "); 
495        } 
496          
497        String lbl = getExtensionLabel(ext); 
498        div.tx(lbl); 
499        div.tx(": "); 
500        render(div, ext.getValue()); 
501      } 
502    } 
503  } 
504   
505  public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 
506    boolean first = true; 
507    for (Extension ext : element.getModifierExtension()) { 
508      if (first) { 
509        first = false; 
510      } else { 
511        div.tx(sep); 
512        div.tx(" "); 
513      } 
514      if (canRender(ext)) { 
515        String lbl = getExtensionLabel(ext); 
516        XhtmlNode b = div.b(); 
517        b.tx(lbl); 
518        b.tx(": "); 
519        render(div, ext.getValue()); 
520      } else { 
521        // somehow have to do better than this  
522        div.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 
523      } 
524    } 
525    for (Extension ext : element.getExtension()) { 
526      if (canRender(ext)) { 
527        if (first) { 
528          first = false; 
529        } else { 
530          div.tx(sep); 
531          div.tx(" "); 
532        } 
533          
534        String lbl = getExtensionLabel(ext); 
535        div.tx(lbl); 
536        div.tx(": "); 
537        render(div, ext.getValue()); 
538      } 
539    } 
540 
541  } 
542   
543  // -- 6. Data type Rendering ----------------------------------------------  
544 
545  public static String display(IWorkerContext context, DataType type) { 
546    return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE)).display(type); 
547  } 
548   
549  public String displayBase(Base b) { 
550    if (b instanceof DataType) { 
551      return display((DataType) b); 
552    } else { 
553      return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " ");       
554    } 
555  } 
556   
557  public String display(DataType type) { 
558    if (type == null || type.isEmpty()) { 
559      return ""; 
560    } 
561     
562    if (type instanceof Coding) { 
563      return displayCoding((Coding) type); 
564    } else if (type instanceof CodeableConcept) { 
565      return displayCodeableConcept((CodeableConcept) type); 
566    } else if (type instanceof Identifier) { 
567      return displayIdentifier((Identifier) type); 
568    } else if (type instanceof HumanName) { 
569      return displayHumanName((HumanName) type); 
570    } else if (type instanceof Address) { 
571      return displayAddress((Address) type); 
572    } else if (type instanceof ContactPoint) { 
573      return displayContactPoint((ContactPoint) type); 
574    } else if (type instanceof Quantity) { 
575      return displayQuantity((Quantity) type); 
576    } else if (type instanceof Range) { 
577      return displayRange((Range) type); 
578    } else if (type instanceof Period) { 
579      return displayPeriod((Period) type); 
580    } else if (type instanceof Timing) { 
581      return displayTiming((Timing) type); 
582    } else if (type instanceof SampledData) { 
583      return displaySampledData((SampledData) type); 
584    } else if (type instanceof ContactDetail) { 
585      return displayContactDetail((ContactDetail) type); 
586    } else if (type.isDateTime()) { 
587      return displayDateTime((BaseDateTimeType) type); 
588    } else if (type.isPrimitive()) { 
589      return context.getTranslated((PrimitiveType<?>) type); 
590    } else { 
591      return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 
592    } 
593  } 
594 
595  protected String displayDateTime(BaseDateTimeType type) { 
596    if (!type.hasPrimitiveValue()) { 
597      return ""; 
598    } 
599     
600    // relevant inputs in rendering context: 
601    // timeZone, dateTimeFormat, locale, mode 
602    //   timezone - application specified timezone to use.  
603    //        null = default to the time of the date/time itself 
604    //   dateTimeFormat - application specified format for date times 
605    //        null = default to ... depends on mode 
606    //   mode - if rendering mode is technical, format defaults to XML format 
607    //   locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale)   
608    if (isOnlyDate(type.getPrecision())) { 
609       
610      DateTimeFormatter fmt = getDateFormatForPrecision(type);       
611      LocalDate date = LocalDate.of(type.getYear(), type.getMonth()+1, type.getDay()); 
612      return fmt.format(date); 
613    } 
614 
615    DateTimeFormatter fmt = context.getDateTimeFormat(); 
616    if (fmt == null) { 
617      if (context.isTechnicalMode()) { 
618        fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 
619      } else { 
620        fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 
621      } 
622    } 
623    ZonedDateTime zdt = ZonedDateTime.parse(type.primitiveValue()); 
624    ZoneId zone = context.getTimeZoneId(); 
625    if (zone != null) { 
626      zdt = zdt.withZoneSameInstant(zone); 
627    } 
628    return fmt.format(zdt); 
629  } 
630 
631  private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 
632    DateTimeFormatter fmt = getContextDateFormat(type); 
633    if (fmt != null) { 
634      return fmt; 
635    } 
636    if (context.isTechnicalMode()) { 
637      switch (type.getPrecision()) { 
638      case YEAR: 
639        return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 
640      case MONTH: 
641        return  new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 
642      default: 
643        return DateTimeFormatter.ISO_DATE; 
644      } 
645    } else { 
646      switch (type.getPrecision()) { 
647      case YEAR: 
648        return DateTimeFormatter.ofPattern("uuuu"); 
649      case MONTH: 
650        return DateTimeFormatter.ofPattern("MMM uuuu"); 
651      default: 
652        return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 
653      } 
654    } 
655  } 
656 
657  private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 
658    switch (type.getPrecision()) { 
659    case YEAR: 
660      return context.getDateYearFormat(); 
661    case MONTH: 
662      return context.getDateYearMonthFormat(); 
663    default: 
664      return context.getDateFormat(); 
665    } 
666  }    
667   
668  private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 
669    return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 
670  } 
671 
672  public String display(BaseWrapper type) { 
673    return (context.formatPhrase(RenderingContext.DATA_REND_TO_DO));    
674  } 
675 
676  public void render(XhtmlNode x, BaseWrapper type) throws FHIRFormatError, DefinitionException, IOException  { 
677    Base base = null; 
678    try { 
679      base = type.getBase(); 
680    } catch (FHIRException | IOException e) { 
681      x.tx(context.formatPhrase(RenderingContext.DATA_REND_ERROR, e.getMessage()) + " "); // this shouldn't happen - it's an error in the library itself 
682      return; 
683    } 
684    if (base instanceof DataType) { 
685      render(x, (DataType) base); 
686    } else { 
687      x.tx(context.formatPhrase(RenderingContext.DATA_REND_TO_DO, base.fhirType())); 
688    } 
689  } 
690   
691  public void renderBase(XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 
692    if (b instanceof DataType) { 
693      render(x, (DataType) b); 
694    } else { 
695      x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " ");       
696    } 
697  } 
698   
699  public void render(XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 
700    if (type instanceof BaseDateTimeType) { 
701      x.tx(displayDateTime((BaseDateTimeType) type)); 
702    } else if (type instanceof UriType) { 
703      renderUri(x, (UriType) type); 
704    } else if (type instanceof Annotation) { 
705      renderAnnotation(x, (Annotation) type); 
706    } else if (type instanceof Coding) { 
707      renderCodingWithDetails(x, (Coding) type); 
708    } else if (type instanceof CodeableConcept) { 
709      renderCodeableConcept(x, (CodeableConcept) type); 
710    } else if (type instanceof Identifier) { 
711      renderIdentifier(x, (Identifier) type); 
712    } else if (type instanceof HumanName) { 
713      renderHumanName(x, (HumanName) type); 
714    } else if (type instanceof Address) { 
715      renderAddress(x, (Address) type); 
716    } else if (type instanceof Expression) { 
717      renderExpression(x, (Expression) type); 
718    } else if (type instanceof Money) { 
719      renderMoney(x, (Money) type); 
720    } else if (type instanceof ContactPoint) { 
721      renderContactPoint(x, (ContactPoint) type); 
722    } else if (type instanceof Quantity) { 
723      renderQuantity(x, (Quantity) type); 
724    } else if (type instanceof Range) { 
725      renderRange(x, (Range) type); 
726    } else if (type instanceof Period) { 
727      renderPeriod(x, (Period) type); 
728    } else if (type instanceof Timing) { 
729      renderTiming(x, (Timing) type); 
730    } else if (type instanceof SampledData) { 
731      renderSampledData(x, (SampledData) type); 
732    } else if (type instanceof Reference) { 
733      renderReference(x, (Reference) type); 
734    } else if (type instanceof UsageContext) { 
735      renderUsageContext(x, (UsageContext) type); 
736    } else if (type instanceof CodeableReference) { 
737      CodeableReference cr = (CodeableReference) type; 
738      if (cr.hasConcept()) { 
739        renderCodeableConcept(x, cr.getConcept()); 
740      } else {  
741        renderReference(x, cr.getReference()); 
742      } 
743    } else if (type instanceof MarkdownType) { 
744      addMarkdown(x, context.getTranslated((MarkdownType) type)); 
745    } else if (type instanceof Base64BinaryType) { 
746      Base64BinaryType b64 = (Base64BinaryType) type; 
747      x.tx(context.formatPhrase(RenderingContext.DATA_REND_BASE64, (b64.getValue() == null ? "0" : b64.getValue().length))); 
748    } else if (type.isPrimitive()) { 
749      x.tx(context.getTranslated((PrimitiveType<?>) type)); 
750    } else { 
751      x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " ");       
752    } 
753  } 
754 
755  protected void renderReference(XhtmlNode x, Reference ref) { 
756     if (ref.hasDisplay()) { 
757       x.tx(context.getTranslated(ref.getDisplayElement())); 
758     } else if (ref.hasReference()) { 
759       x.tx(ref.getReference()); 
760     } else { 
761       x.tx("??"); 
762     } 
763  } 
764 
765  public void renderDateTime(XhtmlNode x, Base e) { 
766    if (e.hasPrimitiveValue()) { 
767      x.addText(displayDateTime((DateTimeType) e)); 
768    } 
769  } 
770 
771  public void renderDate(XhtmlNode x, Base e) { 
772    if (e.hasPrimitiveValue()) { 
773      x.addText(displayDateTime((DateType) e)); 
774    } 
775  } 
776 
777  public void renderDateTime(XhtmlNode x, String s) { 
778    if (s != null) { 
779      DateTimeType dt = new DateTimeType(s); 
780      x.addText(displayDateTime(dt)); 
781    } 
782  } 
783 
784  protected void renderUri(XhtmlNode x, UriType uri) { 
785    if (uri.getValue().startsWith("mailto:")) { 
786      x.ah(uri.getValue()).addText(uri.getValue().substring(7)); 
787    } else { 
788      Resource r = context.getContext().fetchResource(Resource.class, uri.getValue()); 
789      if (r != null && r.getWebPath() != null) { 
790        if (r instanceof CanonicalResource) { 
791          x.ah(r.getWebPath()).addText(crPresent((CanonicalResource) r));           
792        } else { 
793          x.ah(r.getWebPath()).addText(uri.getValue());           
794        } 
795      } else { 
796        String url = context.getResolver() != null ? context.getResolver().resolveUri(context, uri.getValue()) : null; 
797        if (url != null) {           
798          x.ah(url).addText(uri.getValue()); 
799        } else if (Utilities.isAbsoluteUrlLinkable(uri.getValue()) && !(uri instanceof IdType)) { 
800          x.ah(uri.getValue()).addText(uri.getValue()); 
801        } else { 
802          x.addText(uri.getValue()); 
803        } 
804      } 
805    } 
806  } 
807   
808  protected void renderUri(XhtmlNode x, UriType uriD, String path, String id, Resource src) { 
809    String uri = uriD.getValue(); 
810    if (isCanonical(path)) { 
811      x.code().tx(uri); 
812    } else { 
813      if (uri == null) { 
814        x.b().tx(uri); 
815      } else if (uri.startsWith("mailto:")) { 
816        x.ah(uri).addText(uri.substring(7)); 
817      } else { 
818        Resource target = context.getContext().fetchResource(Resource.class, uri, src); 
819        if (target != null && target.hasWebPath()) { 
820          String title = target instanceof CanonicalResource ? crPresent((CanonicalResource) target) : uri; 
821          x.ah(target.getWebPath()).addText(title); 
822        } else { 
823          String url = context.getResolver() != null ? context.getResolver().resolveUri(context, uri) : null; 
824          if (url != null) {           
825            x.ah(url).addText(uri); 
826          } else if (uri.contains("|")) { 
827            x.ah(uri.substring(0, uri.indexOf("|"))).addText(uri); 
828          } else if (uri.startsWith("http:") || uri.startsWith("https:") || uri.startsWith("ftp:")) { 
829            x.ah(uri).addText(uri);         
830          } else { 
831            x.code().addText(uri);         
832          } 
833        } 
834      } 
835    } 
836  } 
837 
838  protected void renderAnnotation(XhtmlNode x, Annotation annot) { 
839    renderAnnotation(x, annot, false); 
840  } 
841 
842  protected void renderAnnotation(XhtmlNode x, Annotation a, boolean showCodeDetails) throws FHIRException { 
843    StringBuilder b = new StringBuilder(); 
844    if (a.hasText()) { 
845      b.append(context.getTranslated(a.getTextElement())); 
846    } 
847 
848    if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { 
849      b.append(" ("); 
850    } 
851 
852    if (a.hasAuthor()) { 
853      b.append(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 
854      if (a.hasAuthorReference()) { 
855        b.append(a.getAuthorReference().getReference()); 
856      } else if (a.hasAuthorStringType()) { 
857        b.append(context.getTranslated(a.getAuthorStringType())); 
858      } 
859    } 
860 
861 
862    if (a.hasTimeElement()) { 
863      if (b.length() > 0) { 
864        b.append(" "); 
865      } 
866      b.append("@").append(a.getTimeElement().toHumanDisplay()); 
867    } 
868    if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { 
869      b.append(")"); 
870    } 
871 
872 
873    x.addText(b.toString()); 
874  } 
875 
876  public String displayCoding(Coding c) { 
877    String s = ""; 
878    if (context.isTechnicalMode()) { 
879      s = context.getTranslated(c.getDisplayElement()); 
880      if (Utilities.noString(s)) { 
881        s = lookupCode(c.getSystem(), c.getVersion(), c.getCode());         
882      } 
883      if (Utilities.noString(s)) { 
884        s = displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode()); 
885      } else if (c.hasSystem()) { 
886        s = s + " ("+displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode())+")"; 
887      } else if (c.hasCode()) { 
888        s = s + " ("+c.getCode()+")"; 
889      } 
890    } else { 
891    if (c.hasDisplayElement()) 
892      return context.getTranslated(c.getDisplayElement()); 
893    if (Utilities.noString(s)) 
894      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
895    if (Utilities.noString(s)) 
896      s = c.getCode(); 
897    } 
898    return s; 
899  } 
900 
901  private String displayCodeSource(String system, String version) { 
902    String s = displaySystem(system); 
903    if (version != null) { 
904      s = s + "["+describeVersion(version)+"]"; 
905    } 
906    return s;     
907  } 
908   
909  private String displayCodeTriple(String system, String version, String code) { 
910    if (system == null) { 
911      if (code == null) { 
912        return ""; 
913      } else { 
914        return "#"+code; 
915      } 
916    } else { 
917      String s = displayCodeSource(system, version); 
918      if (code != null) { 
919        s = s + "#"+code; 
920      } 
921      return s; 
922    } 
923  } 
924 
925  public String displayCoding(List<Coding> list) { 
926    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
927    for (Coding c : list) { 
928      b.append(displayCoding(c)); 
929    } 
930    return b.toString(); 
931  } 
932 
933  protected void renderCoding(XhtmlNode x, Coding c) { 
934    renderCoding(x, c, false); 
935  } 
936   
937  protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 
938    if (c.isEmpty()) { 
939      return; 
940    } 
941 
942    String url = getLinkForSystem(c.getSystem(), c.getVersion()); 
943    String name = displayCodeSource(c.getSystem(), c.getVersion()); 
944    if (!Utilities.noString(url)) { 
945      pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
946    } else {  
947      pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
948    } 
949    pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 
950    String s = context.getTranslated(c.getDisplayElement()); 
951    if (Utilities.noString(s)) { 
952      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
953    } 
954    if (!Utilities.noString(s)) { 
955      pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 
956    } 
957  } 
958   
959  private String getLinkForSystem(String system, String version) { 
960    if ("http://snomed.info/sct".equals(system)) { 
961      return "https://browser.ihtsdotools.org/";       
962    } else if ("http://loinc.org".equals(system)) { 
963      return "https://loinc.org/";             
964    } else if ("http://unitsofmeasure.org".equals(system)) { 
965      return "http://ucum.org";             
966    } else { 
967      String url = system; 
968      if (version != null) { 
969        url = url + "|"+version; 
970      } 
971      CodeSystem cs = context.getWorker().fetchCodeSystem(url); 
972      if (cs != null && cs.hasWebPath()) { 
973        return cs.getWebPath(); 
974      } 
975      return null; 
976    } 
977  } 
978   
979  protected String getLinkForCode(String system, String version, String code) { 
980    if ("http://snomed.info/sct".equals(system)) { 
981      if (!Utilities.noString(code)) { 
982        return "http://snomed.info/id/"+code;         
983      } else { 
984        return "https://browser.ihtsdotools.org/"; 
985      } 
986    } else if ("http://loinc.org".equals(system)) { 
987      if (!Utilities.noString(code)) { 
988        return "https://loinc.org/"+code; 
989      } else { 
990        return "https://loinc.org/"; 
991      } 
992    } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 
993      if (!Utilities.noString(code)) { 
994        return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code;         
995      } else { 
996        return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 
997      } 
998    } else if ("urn:iso:std:iso:3166".equals(system)) { 
999      if (!Utilities.noString(code)) { 
1000        return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code;         
1001      } else { 
1002        return "https://en.wikipedia.org/wiki/ISO_3166-2"; 
1003      } 
1004    } else { 
1005      CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 
1006      if (cs != null && cs.hasWebPath()) { 
1007        if (!Utilities.noString(code)) { 
1008          return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 
1009        } else { 
1010          return cs.getWebPath(); 
1011        } 
1012      } 
1013    }   
1014    return null; 
1015  } 
1016   
1017  public CodeResolution resolveCode(String system, String code) { 
1018    return resolveCode(new Coding().setSystem(system).setCode(code)); 
1019  } 
1020 
1021  public CodeResolution resolveCode(Coding c) { 
1022    String systemName; 
1023    String systemLink; 
1024    String link; 
1025    String display = null; 
1026    String hint; 
1027     
1028    if (c.hasDisplayElement()) 
1029      display = context.getTranslated(c.getDisplayElement()); 
1030    if (Utilities.noString(display)) 
1031      display = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1032    if (Utilities.noString(display)) { 
1033      display = c.getCode(); 
1034    } 
1035     
1036    CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); 
1037    systemLink = cs != null ? cs.getWebPath() : null; 
1038    systemName = cs != null ? crPresent(cs) : displaySystem(c.getSystem()); 
1039    link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()); 
1040 
1041    hint = systemName+": "+display+(c.hasVersion() ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.getVersion(), ")") : ""); 
1042    return new CodeResolution(systemName, systemLink, link, display, hint); 
1043  } 
1044   
1045  public CodeResolution resolveCode(CodeableConcept code) { 
1046    if (code.hasCoding()) { 
1047      return resolveCode(code.getCodingFirstRep()); 
1048    } else { 
1049      return new CodeResolution(null, null, null, code.getText(), code.getText()); 
1050    } 
1051  } 
1052  protected void renderCodingWithDetails(XhtmlNode x, Coding c) { 
1053    String s = ""; 
1054    if (c.hasDisplayElement()) 
1055      s = context.getTranslated(c.getDisplayElement()); 
1056    if (Utilities.noString(s)) 
1057      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1058 
1059    CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); 
1060 
1061    String sn = cs != null ? crPresent(cs) : displaySystem(c.getSystem()); 
1062    String link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()); 
1063    if (link != null) { 
1064      x.ah(link).tx(sn); 
1065    } else { 
1066      x.tx(sn); 
1067    } 
1068     
1069    x.tx(" "); 
1070    x.tx(c.getCode()); 
1071    if (!Utilities.noString(s)) { 
1072      x.tx(": "); 
1073      x.tx(s); 
1074    } 
1075    if (c.hasVersion()) { 
1076      x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.getVersion(), ")")); 
1077    } 
1078  } 
1079   
1080  protected void renderCoding(XhtmlNode x, Coding c, boolean showCodeDetails) { 
1081    String s = ""; 
1082    if (c.hasDisplayElement()) 
1083      s = context.getTranslated(c.getDisplayElement()); 
1084    if (Utilities.noString(s)) 
1085      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1086 
1087    if (Utilities.noString(s)) 
1088      s = c.getCode(); 
1089 
1090    if (showCodeDetails) { 
1091      x.addText(s+" "+context.formatPhrase(RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.getSystem()), c.getCode(), " = '", lookupCode(c.getSystem(), c.getVersion(), c.getCode()), c.getDisplay(), "')")); 
1092    } else 
1093      x.span(null, "{"+c.getSystem()+" "+c.getCode()+"}").addText(s); 
1094  } 
1095 
1096  public String displayCodeableConcept(CodeableConcept cc) { 
1097    String s = context.getTranslated(cc.getTextElement()); 
1098    if (Utilities.noString(s)) { 
1099      for (Coding c : cc.getCoding()) { 
1100        if (c.hasDisplayElement()) { 
1101          s = context.getTranslated(c.getDisplayElement()); 
1102          break; 
1103        } 
1104      } 
1105    } 
1106    if (Utilities.noString(s)) { 
1107      // still? ok, let's try looking it up 
1108      for (Coding c : cc.getCoding()) { 
1109        if (c.hasCode() && c.hasSystem()) { 
1110          s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1111          if (!Utilities.noString(s)) 
1112            break; 
1113        } 
1114      } 
1115    } 
1116 
1117    if (Utilities.noString(s)) { 
1118      if (cc.getCoding().isEmpty()) 
1119        s = ""; 
1120      else 
1121        s = cc.getCoding().get(0).getCode(); 
1122    } 
1123    return s; 
1124  } 
1125 
1126  protected void renderCodeableConcept(XhtmlNode x, CodeableConcept cc) throws FHIRFormatError, DefinitionException, IOException { 
1127    renderCodeableConcept(x, cc, false); 
1128  } 
1129   
1130  protected void renderCodeableReference(XhtmlNode x, CodeableReference e, boolean showCodeDetails) throws FHIRFormatError, DefinitionException, IOException { 
1131    if (e.hasConcept()) { 
1132      renderCodeableConcept(x, e.getConcept(), showCodeDetails); 
1133    } 
1134    if (e.hasReference()) { 
1135      renderReference(x, e.getReference()); 
1136    } 
1137  } 
1138 
1139  protected void renderCodeableConcept(XhtmlNode x, CodeableConcept cc, boolean showCodeDetails) throws FHIRFormatError, DefinitionException, IOException { 
1140    if (cc.isEmpty()) { 
1141      return; 
1142    } 
1143 
1144    String s = context.getTranslated(cc.getTextElement()); 
1145    if (Utilities.noString(s)) { 
1146      for (Coding c : cc.getCoding()) { 
1147        if (c.hasDisplayElement()) { 
1148          s = context.getTranslated(c.getDisplayElement()); 
1149          break; 
1150        } 
1151      } 
1152    } 
1153    if (Utilities.noString(s)) { 
1154      // still? ok, let's try looking it up 
1155      for (Coding c : cc.getCoding()) { 
1156        if (c.hasCodeElement() && c.hasSystemElement()) { 
1157          s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1158          if (!Utilities.noString(s)) 
1159            break; 
1160        } 
1161      } 
1162    } 
1163 
1164    if (Utilities.noString(s)) { 
1165      if (cc.getCoding().isEmpty()) 
1166        s = ""; 
1167      else 
1168        s = cc.getCoding().get(0).getCode(); 
1169    } 
1170 
1171    if (showCodeDetails) { 
1172      x.addText(s+" "); 
1173      XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 
1174      sp.tx(" ("); 
1175      boolean first = true; 
1176      for (Coding c : cc.getCoding()) { 
1177        if (first) { 
1178          first = false; 
1179        } else { 
1180          sp.tx("; "); 
1181        } 
1182        String url = getLinkForSystem(c.getSystem(), c.getVersion()); 
1183        if (url != null) { 
1184          sp.ah(url).tx(displayCodeSource(c.getSystem(), c.getVersion())); 
1185        } else { 
1186          sp.tx(displayCodeSource(c.getSystem(), c.getVersion())); 
1187        } 
1188        if (c.hasCode()) { 
1189          sp.tx("#"+c.getCode()); 
1190        } 
1191        if (c.hasDisplay() && !s.equals(c.getDisplay())) { 
1192          sp.tx(" \""+context.getTranslated(c.getDisplayElement())+"\""); 
1193        } 
1194      } 
1195      if (hasRenderableExtensions(cc)) { 
1196        if (!first) { 
1197          sp.tx("; "); 
1198        } 
1199        renderExtensionsInText(sp, cc, ";"); 
1200      } 
1201      sp.tx(")"); 
1202    } else { 
1203 
1204      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1205      for (Coding c : cc.getCoding()) { 
1206        if (c.hasCodeElement() && c.hasSystemElement()) { 
1207          b.append("{"+c.getSystem()+" "+c.getCode()+"}"); 
1208        } 
1209      } 
1210 
1211      x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); 
1212    } 
1213  } 
1214 
1215  protected String displayIdentifier(Identifier ii) { 
1216    String s = Utilities.noString(ii.getValue()) ? "?ngen-9?" : ii.getValue(); 
1217    if ("urn:ietf:rfc:3986".equals(ii.getSystem()) && s.startsWith("urn:oid:")) { 
1218      s = "OID:"+s.substring(8); 
1219    } else if ("urn:ietf:rfc:3986".equals(ii.getSystem()) && s.startsWith("urn:uuid:")) { 
1220      s = "UUID:"+s.substring(9); 
1221    } else {  
1222      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.getSystem()); 
1223      if (ns != null) { 
1224        s = crPresent(ns)+"#"+s; 
1225      } 
1226      if (ii.hasType()) { 
1227        if (ii.getType().hasText()) 
1228          s = context.getTranslated(ii.getType().getTextElement())+":\u00A0"+s; 
1229        else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) 
1230          s = context.getTranslated(ii.getType().getCoding().get(0).getDisplayElement())+": "+s; 
1231        else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) 
1232          s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getVersion(), ii.getType().getCoding().get(0).getCode())+": "+s; 
1233      } else if (ii.hasSystem()) { 
1234        s = ii.getSystem()+"#"+s; 
1235      } 
1236    } 
1237 
1238    if (ii.hasUse() || ii.hasPeriod()) { 
1239      s = s + "\u00A0("; 
1240      if (ii.hasUse()) { 
1241        s = s + "use:\u00A0"+ii.getUse().toCode(); 
1242      } 
1243      if (ii.hasUse() && ii.hasPeriod()) { 
1244        s = s + ",\u00A0"; 
1245      } 
1246      if (ii.hasPeriod()) { 
1247        s = s + "period:\u00A0"+displayPeriod(ii.getPeriod()); 
1248      } 
1249      s = s + ")"; 
1250    }     
1251    return s; 
1252  } 
1253   
1254  protected void renderIdentifier(XhtmlNode x, Identifier ii) {     
1255    if (ii.hasType()) { 
1256      if (ii.getType().hasText()) { 
1257        x.tx(context.getTranslated(ii.getType().getTextElement())); 
1258      } else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) { 
1259        x.tx(context.getTranslated(ii.getType().getCoding().get(0).getDisplayElement())); 
1260      } else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) { 
1261        x.tx(lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getVersion(), ii.getType().getCoding().get(0).getCode())); 
1262      } 
1263      x.tx("/"); 
1264    } else if (ii.hasSystem()) { 
1265      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.getSystem()); 
1266      if (ns != null) { 
1267        if (ns.hasWebPath()) { 
1268          x.ah(ns.getWebPath(), ns.getDescription()).tx(crPresent(ns));         
1269        } else { 
1270          x.tx(crPresent(ns)); 
1271        } 
1272      } else { 
1273        switch (ii.getSystem()) { 
1274        case "urn:oid:2.51.1.3": 
1275          x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 
1276          break; 
1277        default: 
1278          x.code(ii.getSystem());       
1279        } 
1280      } 
1281      x.tx("/"); 
1282    } 
1283    x.tx(Utilities.noString(ii.getValue()) ? "?ngen-9?" : ii.getValue()); 
1284 
1285    if (ii.hasUse() || ii.hasPeriod()) { 
1286      x.nbsp(); 
1287      x.tx("("); 
1288      if (ii.hasUse()) { 
1289        x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 
1290        x.nbsp(); 
1291        x.tx(ii.getUse().toCode()); 
1292      } 
1293      if (ii.hasUse() && ii.hasPeriod()) { 
1294        x.tx(","); 
1295        x.nbsp(); 
1296      } 
1297      if (ii.hasPeriod()) { 
1298        x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 
1299        x.nbsp(); 
1300        x.tx(displayPeriod(ii.getPeriod())); 
1301      } 
1302      x.tx(")"); 
1303    }         
1304  } 
1305 
1306  public static String displayHumanName(HumanName name) { 
1307    StringBuilder s = new StringBuilder(); 
1308    if (name.hasText()) 
1309      s.append(name.getText()); 
1310    else { 
1311      for (StringType p : name.getGiven()) { 
1312        s.append(p.getValue()); 
1313        s.append(" "); 
1314      } 
1315      if (name.hasFamily()) { 
1316        s.append(name.getFamily()); 
1317        s.append(" "); 
1318      } 
1319    } 
1320    if (name.hasUse() && name.getUse() != NameUse.USUAL) 
1321      s.append("("+name.getUse().toString()+")"); 
1322    return s.toString(); 
1323  } 
1324 
1325 
1326  protected void renderHumanName(XhtmlNode x, HumanName name) { 
1327    StringBuilder s = new StringBuilder(); 
1328    if (name.hasText()) 
1329      s.append(context.getTranslated(name.getTextElement())); 
1330    else { 
1331      for (StringType p : name.getGiven()) { 
1332        s.append(context.getTranslated(p)); 
1333        s.append(" "); 
1334      } 
1335      if (name.hasFamily()) { 
1336        s.append(context.getTranslated(name.getFamilyElement())); 
1337        s.append(" "); 
1338      } 
1339    } 
1340    if (name.hasUse() && name.getUse() != NameUse.USUAL) 
1341      s.append("("+context.getTranslatedCode(name.getUseElement(), "http://hl7.org/fhir/name-use")+")"); 
1342     
1343    x.addText(s.toString()); 
1344  } 
1345 
1346  private String displayAddress(Address address) { 
1347    StringBuilder s = new StringBuilder(); 
1348    if (address.hasText()) 
1349      s.append(context.getTranslated(address.getTextElement())); 
1350    else { 
1351      for (StringType p : address.getLine()) { 
1352        s.append(context.getTranslated(p)); 
1353        s.append(" "); 
1354      } 
1355      if (address.hasCity()) { 
1356        s.append(context.getTranslated(address.getCityElement())); 
1357        s.append(" "); 
1358      } 
1359      if (address.hasState()) { 
1360        s.append(context.getTranslated(address.getStateElement())); 
1361        s.append(" "); 
1362      } 
1363 
1364      if (address.hasPostalCode()) { 
1365        s.append(context.getTranslated(address.getPostalCodeElement())); 
1366        s.append(" "); 
1367      } 
1368 
1369      if (address.hasCountry()) { 
1370        s.append(context.getTranslated(address.getCountryElement())); 
1371        s.append(" "); 
1372      } 
1373    } 
1374    if (address.hasUse()) 
1375      s.append("("+address.getUse().toCode()+")"); 
1376    return s.toString(); 
1377  } 
1378   
1379  protected void renderAddress(XhtmlNode x, Address address) { 
1380    x.addText(displayAddress(address)); 
1381  } 
1382 
1383 
1384  public static String displayContactPoint(ContactPoint contact) { 
1385    StringBuilder s = new StringBuilder(); 
1386    s.append(describeSystem(contact.getSystem())); 
1387    if (Utilities.noString(contact.getValue())) 
1388      s.append("-unknown-"); 
1389    else 
1390      s.append(contact.getValue()); 
1391    if (contact.hasUse()) 
1392      s.append("("+contact.getUse().toString()+")"); 
1393    return s.toString(); 
1394  } 
1395 
1396  public static String displayContactDetail(ContactDetail contact) { 
1397    CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 
1398    for (ContactPoint cp : contact.getTelecom()) { 
1399      s.append(displayContactPoint(cp)); 
1400    } 
1401    return contact.getName()+(s.length() == 0 ? "" : " ("+s.toString()+")"); 
1402  } 
1403 
1404  protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 
1405    NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 
1406    numberFormat.setGroupingUsed(true); 
1407    numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 
1408    numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 
1409    return numberFormat.format(input); 
1410} 
1411   
1412  protected void renderMoney(XhtmlNode x, Money money) { 
1413    if (x.getName().equals("blockquote")) { 
1414      x = x.para(); 
1415    } 
1416    Currency c = money.hasCurrency() ? Currency.getInstance(money.getCurrency()) : null; 
1417    if (c != null) { 
1418      XhtmlNode s = x.span(null, c.getDisplayName()); 
1419      s.tx(c.getSymbol(context.getLocale())); 
1420      s.tx(getLocalizedBigDecimalValue(money.getValue(), c)); 
1421      x.tx(" ("+c.getCurrencyCode()+")"); 
1422    } else { 
1423      if (money.getCurrency() != null) { 
1424        x.tx(money.getCurrency()); 
1425      } 
1426      x.tx(money.getValue().toPlainString()); 
1427    } 
1428  } 
1429   
1430  protected void renderExpression(XhtmlNode x, Expression expr) { 
1431  // there's two parts: what the expression is, and how it's described.  
1432    // we start with what it is, and then how it's described  
1433    XhtmlNode p = x; 
1434    if (p.getName().equals("blockquote")) { 
1435      p = p.para(); 
1436    } 
1437    if (expr.hasExpression()) { 
1438      if (expr.hasReference()) { 
1439        p = x.ah(expr.getReference());         
1440      } 
1441      XhtmlNode c = p; 
1442      if (expr.hasLanguage()) { 
1443        c = c.span(null, expr.getLanguage()); 
1444      } 
1445      c.code().tx(expr.getExpression()); 
1446    } else if (expr.hasReference()) { 
1447      p.ah(expr.getReference()).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 
1448    } 
1449    if (expr.hasName() || expr.hasDescription()) { 
1450      p.tx("("); 
1451      if (expr.hasName()) { 
1452        p.b().tx(expr.getName()); 
1453      } 
1454      if (expr.hasDescription()) { 
1455        p.tx("\""); 
1456        p.tx(context.getTranslated(expr.getDescriptionElement())); 
1457        p.tx("\""); 
1458      } 
1459      p.tx(")"); 
1460    } 
1461  } 
1462   
1463   
1464  protected void renderContactPoint(XhtmlNode x, ContactPoint contact) { 
1465    if (contact != null) { 
1466      if (!contact.hasSystem()) { 
1467        x.addText(displayContactPoint(contact));         
1468      } else { 
1469        switch (contact.getSystem()) { 
1470        case EMAIL: 
1471          x.ah("mailto:"+contact.getValue()).tx(contact.getValue()); 
1472          break; 
1473        case FAX: 
1474          x.addText(displayContactPoint(contact)); 
1475          break; 
1476        case NULL: 
1477          x.addText(displayContactPoint(contact)); 
1478          break; 
1479        case OTHER: 
1480          x.addText(displayContactPoint(contact)); 
1481          break; 
1482        case PAGER: 
1483          x.addText(displayContactPoint(contact)); 
1484          break; 
1485        case PHONE: 
1486          if (contact.hasValue() && contact.getValue() != null && contact.getValue().startsWith("+")) { 
1487            x.ah("tel:"+contact.getValue().replace(" ", "")).tx(contact.getValue()); 
1488          } else { 
1489            x.addText(displayContactPoint(contact)); 
1490          } 
1491          break; 
1492        case SMS: 
1493          x.addText(displayContactPoint(contact)); 
1494          break; 
1495        case URL: 
1496          x.ah(contact.getValue()).tx(contact.getValue()); 
1497          break; 
1498        default: 
1499          break;       
1500        } 
1501      } 
1502    } 
1503  } 
1504 
1505  protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 
1506    if (c != null) { 
1507      if (c.getSystem() == ContactPointSystem.PHONE) { 
1508        p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 
1509      } else if (c.getSystem() == ContactPointSystem.FAX) { 
1510        p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 
1511      } else if (c.getSystem() == ContactPointSystem.EMAIL) { 
1512        p.tx(c.getValue()); 
1513      } else if (c.getSystem() == ContactPointSystem.URL) { 
1514        if (c.getValue().length() > 30) { 
1515          p.addText(c.getValue().substring(0, 30)+"..."); 
1516        } else { 
1517          p.addText(c.getValue()); 
1518        } 
1519      } 
1520    } 
1521  } 
1522 
1523  protected void addTelecom(XhtmlNode p, ContactPoint c) { 
1524    if (c.getSystem() == ContactPointSystem.PHONE) { 
1525      p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 
1526    } else if (c.getSystem() == ContactPointSystem.FAX) { 
1527      p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 
1528    } else if (c.getSystem() == ContactPointSystem.EMAIL) { 
1529      p.ah("mailto:"+c.getValue()).addText(c.getValue()); 
1530    } else if (c.getSystem() == ContactPointSystem.URL) { 
1531      if (c.getValue().length() > 30) 
1532        p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"..."); 
1533      else 
1534        p.ah(c.getValue()).addText(c.getValue()); 
1535    } 
1536  } 
1537  private static String describeSystem(ContactPointSystem system) { 
1538    if (system == null) 
1539      return ""; 
1540    switch (system) { 
1541    case PHONE: return "ph: "; 
1542    case FAX: return "fax: "; 
1543    default: 
1544      return ""; 
1545    } 
1546  } 
1547 
1548  protected String displayQuantity(Quantity q) { 
1549    StringBuilder s = new StringBuilder(); 
1550 
1551    s.append(q.hasValue() ? q.getValue() : "?"); 
1552    if (q.hasUnit()) 
1553      s.append(" ").append(q.getUnit()); 
1554    else if (q.hasCode()) 
1555      s.append(" ").append(q.getCode()); 
1556 
1557    return s.toString(); 
1558  }   
1559   
1560  protected void renderQuantity(XhtmlNode x, Quantity q) { 
1561    renderQuantity(x, q, false); 
1562  } 
1563   
1564  protected void renderQuantity(XhtmlNode x, Quantity q, boolean showCodeDetails) { 
1565    if (q.hasComparator()) 
1566      x.addText(q.getComparator().toCode()); 
1567    if (q.hasValue()) { 
1568      x.addText(context.getTranslated(q.getValueElement())); 
1569    } 
1570    if (q.hasUnit()) 
1571      x.tx(" "+context.getTranslated(q.getUnitElement())); 
1572    else if (q.hasCode() && q.hasSystem()) { 
1573      // if there's a code there *shall* be a system, so if we've got one and not the other, things are invalid and we won't bother trying to render 
1574      if (q.hasSystem() && q.getSystem().equals("http://unitsofmeasure.org")) 
1575        x.tx(" "+q.getCode()); 
1576      else 
1577        x.tx("(unit "+q.getCode()+" from "+q.getSystem()+")"); 
1578    } 
1579    if (showCodeDetails && q.hasCode()) { 
1580      x.span("background: LightGoldenRodYellow", null).tx(" "+ (context.formatPhrase(RenderingContext.DATA_REND_DETAILS, displaySystem(q.getSystem()))) +q.getCode()+" = '"+lookupCode(q.getSystem(), null, q.getCode())+"')"); 
1581    } 
1582  } 
1583 
1584   
1585  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 
1586    pieces.add(gen.new Piece(null, displayQuantity(q), null)); 
1587  } 
1588 
1589  public String displayRange(Range q) { 
1590    if (!q.hasLow() && !q.hasHigh()) 
1591      return "?"; 
1592 
1593    StringBuilder b = new StringBuilder(); 
1594 
1595    boolean sameUnits = (q.getLow().hasUnit() && q.getHigh().hasUnit() && q.getLow().getUnit().equals(q.getHigh().getUnit()))  
1596        || (q.getLow().hasCode() && q.getHigh().hasCode() && q.getLow().getCode().equals(q.getHigh().getCode())); 
1597    String low = "?"; 
1598    if (q.hasLow() && q.getLow().hasValue()) 
1599      low = sameUnits ? q.getLow().getValue().toString() : displayQuantity(q.getLow()); 
1600    String high = displayQuantity(q.getHigh()); 
1601    if (high.isEmpty()) 
1602      high = "?"; 
1603    b.append(low).append("\u00A0to\u00A0").append(high); 
1604    return b.toString(); 
1605  } 
1606 
1607  protected void renderRange(XhtmlNode x, Range q) { 
1608    if (q.hasLow()) 
1609      x.addText(q.getLow().getValue().toString()); 
1610    else 
1611      x.tx("?"); 
1612    x.tx("-"); 
1613    if (q.hasHigh()) 
1614      x.addText(q.getHigh().getValue().toString()); 
1615    else 
1616      x.tx("?"); 
1617    if (q.getLow().hasUnit()) 
1618      x.tx(" "+q.getLow().getUnit()); 
1619  } 
1620 
1621  public String displayPeriod(Period p) { 
1622    String s = !p.hasStart() ? "(?)" : displayDateTime(p.getStartElement()); 
1623    s = s + " --> "; 
1624    return s + (!p.hasEnd() ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.getEndElement())); 
1625  } 
1626 
1627  public void renderPeriod(XhtmlNode x, Period p) { 
1628    x.addText(!p.hasStart() ? "??" : displayDateTime(p.getStartElement())); 
1629    x.tx(" --> "); 
1630    x.addText(!p.hasEnd() ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.getEndElement())); 
1631  } 
1632   
1633  public void renderUsageContext(XhtmlNode x, UsageContext u) throws FHIRFormatError, DefinitionException, IOException { 
1634    renderCoding(x,  u.getCode()); 
1635    x.tx(": "); 
1636    render(x, u.getValue());     
1637  } 
1638   
1639   
1640  public void renderTriggerDefinition(XhtmlNode x, TriggerDefinition td) throws FHIRFormatError, DefinitionException, IOException { 
1641    if (x.isPara()) { 
1642      x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1643      x.tx(": "); 
1644      x.tx(td.getType().getDisplay()); 
1645 
1646      if (td.hasName()) {     
1647        x.tx(", "); 
1648        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1649        x.tx(": "); 
1650        x.tx(context.getTranslated(td.getNameElement())); 
1651      } 
1652      if (td.hasCode()) {     
1653        x.tx(", "); 
1654        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1655        x.tx(": "); 
1656        renderCodeableConcept(x, td.getCode()); 
1657      } 
1658      if (td.hasTiming()) {     
1659        x.tx(", "); 
1660        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1661        x.tx(": "); 
1662        render(x, td.getTiming()); 
1663      } 
1664      if (td.hasCondition()) {     
1665        x.tx(", "); 
1666        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1667        x.tx(": "); 
1668        renderExpression(x, td.getCondition()); 
1669      }     
1670    } else { 
1671      XhtmlNode tbl = x.table("grid"); 
1672 
1673      XhtmlNode tr = tbl.tr();   
1674      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1675      tr.td().tx(td.getType().getDisplay()); 
1676 
1677      if (td.hasName()) {     
1678        tr = tbl.tr();   
1679        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1680        tr.td().tx(context.getTranslated(td.getNameElement())); 
1681      } 
1682      if (td.hasCode()) {     
1683        tr = tbl.tr();   
1684        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1685        renderCodeableConcept(tr.td(), td.getCode()); 
1686      } 
1687      if (td.hasTiming()) {     
1688        tr = tbl.tr();   
1689        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1690        render(tr.td(), td.getTiming()); 
1691      } 
1692      if (td.hasCondition()) {     
1693        tr = tbl.tr();   
1694        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1695        renderExpression(tr.td(), td.getCondition()); 
1696      }     
1697    } 
1698  } 
1699   
1700  public void renderDataRequirement(XhtmlNode x, DataRequirement dr) throws FHIRFormatError, DefinitionException, IOException { 
1701    XhtmlNode tbl = x.table("grid"); 
1702    XhtmlNode tr = tbl.tr();     
1703    XhtmlNode td = tr.td().colspan("2"); 
1704    td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1705    td.tx(": "); 
1706    StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.getType().toCode()); 
1707    if (sd != null && sd.hasWebPath()) { 
1708      td.ah(sd.getWebPath()).tx(dr.getType().toCode()); 
1709    } else { 
1710      td.tx(dr.getType().toCode()); 
1711    } 
1712    if (dr.hasProfile()) { 
1713      td.tx(" ("); 
1714      boolean first = true; 
1715      for (CanonicalType p : dr.getProfile()) { 
1716        if (first) first = false; else td.tx(" | "); 
1717        sd = context.getWorker().fetchResource(StructureDefinition.class, p.getValue()); 
1718        if (sd != null && sd.hasWebPath()) { 
1719          td.ah(sd.getWebPath()).tx(crPresent(sd)); 
1720        } else { 
1721            td.tx(p.asStringValue()); 
1722        } 
1723      } 
1724      td.tx(")"); 
1725    } 
1726    if (dr.hasSubject()) { 
1727      tr = tbl.tr();     
1728      td = tr.td().colspan("2"); 
1729      td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 
1730      if (dr.hasSubjectReference()) { 
1731        renderReference(td,  dr.getSubjectReference()); 
1732      } else { 
1733        renderCodeableConcept(td, dr.getSubjectCodeableConcept()); 
1734      } 
1735    } 
1736    if (dr.hasCodeFilter() || dr.hasDateFilter()) { 
1737      tr = tbl.tr().backgroundColor("#efefef");     
1738      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 
1739      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 
1740    } 
1741    for (DataRequirementCodeFilterComponent cf : dr.getCodeFilter()) { 
1742      tr = tbl.tr();     
1743      if (cf.hasPath()) { 
1744        tr.td().tx(cf.getPath()); 
1745      } else { 
1746        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.getSearchParam()) + " "); 
1747      } 
1748      if (cf.hasValueSet()) { 
1749        td = tr.td(); 
1750        td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 
1751        render(td, cf.getValueSetElement()); 
1752      } else { 
1753        boolean first = true; 
1754        td = tr.td(); 
1755        td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 
1756        for (Coding c : cf.getCode()) { 
1757          if (first) first = false; else td.tx(", "); 
1758          render(td, c); 
1759        } 
1760      } 
1761    } 
1762    for (DataRequirementDateFilterComponent cf : dr.getDateFilter()) { 
1763      tr = tbl.tr();     
1764      if (cf.hasPath()) { 
1765        tr.td().tx(cf.getPath()); 
1766      } else { 
1767        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.getSearchParam()) + " "); 
1768      } 
1769      render(tr.td(), cf.getValue()); 
1770    } 
1771    if (dr.hasSort() || dr.hasLimit()) { 
1772      tr = tbl.tr();     
1773      td = tr.td().colspan("2"); 
1774      if (dr.hasLimit()) { 
1775        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 
1776        td.tx(": "); 
1777        td.tx(dr.getLimit()); 
1778        if (dr.hasSort()) { 
1779          td.tx(", "); 
1780        } 
1781      } 
1782      if (dr.hasSort()) { 
1783        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 
1784        td.tx(": "); 
1785        boolean first = true; 
1786        for (DataRequirementSortComponent p : dr.getSort()) { 
1787          if (first) first = false; else td.tx(" | "); 
1788          td.tx(p.getDirection() == SortDirection.ASCENDING ? "+" : "-"); 
1789          td.tx(p.getPath()); 
1790        } 
1791      } 
1792    } 
1793  } 
1794   
1795   
1796  private String displayTiming(Timing s) throws FHIRException { 
1797    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1798    if (s.hasCode()) 
1799      b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.getCode())) + " "); 
1800 
1801    if (s.getEvent().size() > 0) { 
1802      CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 
1803      for (DateTimeType p : s.getEvent()) { 
1804        if (p.hasValue()) { 
1805          c.append(displayDateTime(p)); 
1806        } else if (!renderExpression(c, p)) { 
1807          c.append("??"); 
1808        }         
1809      } 
1810      b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 
1811    } 
1812 
1813    if (s.hasRepeat()) { 
1814      TimingRepeatComponent rep = s.getRepeat(); 
1815      if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart()) 
1816        b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.getBoundsPeriod().getStartElement())) + " "); 
1817      if (rep.hasCount()) 
1818        b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, Integer.toString(rep.getCount())) + " " + " times"); 
1819      if (rep.hasDuration()) 
1820        b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit())) + " "); 
1821 
1822      if (rep.hasWhen()) { 
1823        String st = ""; 
1824        if (rep.hasOffset()) { 
1825          st = Integer.toString(rep.getOffset())+"min "; 
1826        } 
1827        b.append(st); 
1828        for (Enumeration<EventTiming> wh : rep.getWhen()) 
1829          b.append(displayEventCode(wh.getValue())); 
1830      } else { 
1831        String st = ""; 
1832        if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) ) 
1833          st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 
1834        else { 
1835          st = Integer.toString(rep.getFrequency()); 
1836          if (rep.hasFrequencyMax()) 
1837            st = st + "-"+Integer.toString(rep.getFrequency()); 
1838        } 
1839        if (rep.hasPeriod()) { 
1840          st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+rep.getPeriod().toPlainString(); 
1841          if (rep.hasPeriodMax()) 
1842            st = st + "-"+rep.getPeriodMax().toPlainString(); 
1843          st = st + " "+displayTimeUnits(rep.getPeriodUnit()); 
1844        } 
1845        b.append(st); 
1846      } 
1847      if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd()) 
1848        b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.getBoundsPeriod().getEndElement())) + " "); 
1849    } 
1850    return b.toString(); 
1851  } 
1852 
1853  private boolean renderExpression(CommaSeparatedStringBuilder c, PrimitiveType p) { 
1854    Extension exp = p.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 
1855    if (exp == null) { 
1856      return false; 
1857    } 
1858    c.append(exp.getValueExpression().getExpression()); 
1859    return true; 
1860  } 
1861 
1862  private String displayEventCode(EventTiming when) { 
1863    switch (when) { 
1864    case C: return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 
1865    case CD: return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 
1866    case CM: return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 
1867    case CV: return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 
1868    case AC: return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 
1869    case ACD: return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 
1870    case ACM: return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 
1871    case ACV: return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 
1872    case HS: return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 
1873    case PC: return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 
1874    case PCD: return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 
1875    case PCM: return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 
1876    case PCV: return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 
1877    case WAKE: return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 
1878    default: return "?ngen-6?"; 
1879    } 
1880  } 
1881 
1882  private String displayTimeUnits(UnitsOfTime units) { 
1883    if (units == null) 
1884      return "?ngen-7?"; 
1885    switch (units) { 
1886    case A: return "years"; 
1887    case D: return "days"; 
1888    case H: return "hours"; 
1889    case MIN: return "minutes"; 
1890    case MO: return "months"; 
1891    case S: return "seconds"; 
1892    case WK: return "weeks"; 
1893    default: return "?ngen-8?"; 
1894    } 
1895  } 
1896   
1897  protected void renderTiming(XhtmlNode x, Timing s) throws FHIRException { 
1898    x.addText(displayTiming(s)); 
1899  } 
1900 
1901 
1902  private String displaySampledData(SampledData s) { 
1903    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1904    if (s.hasOrigin()) 
1905      b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.getOrigin())) + " "); 
1906 
1907    if (s.hasInterval()) { 
1908      b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.getInterval().toString()) + " "); 
1909 
1910      if (s.hasIntervalUnit()) 
1911        b.append(s.getIntervalUnit().toString()); 
1912    } 
1913 
1914    if (s.hasFactor()) 
1915      b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.getFactor().toString()) + " "); 
1916 
1917    if (s.hasLowerLimit()) 
1918      b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.getLowerLimit().toString()) + " "); 
1919 
1920    if (s.hasUpperLimit()) 
1921      b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.getUpperLimit().toString()) + " "); 
1922 
1923    if (s.hasDimensions()) 
1924      b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.getDimensions()) + " "); 
1925 
1926    if (s.hasData()) 
1927      b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.getData()) + " "); 
1928 
1929    return b.toString(); 
1930  } 
1931 
1932  protected void renderSampledData(XhtmlNode x, SampledData sampledData) { 
1933    x.addText(displaySampledData(sampledData)); 
1934  } 
1935 
1936  public RenderingContext getContext() { 
1937    return context; 
1938  } 
1939   
1940 
1941  public XhtmlNode makeExceptionXhtml(Exception e, String function) { 
1942    XhtmlNode xn; 
1943    xn = new XhtmlNode(NodeType.Element, "div"); 
1944    XhtmlNode p = xn.para(); 
1945    p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 
1946    p.addComment(getStackTrace(e)); 
1947    return xn; 
1948  } 
1949 
1950  private String getStackTrace(Exception e) { 
1951    StringBuilder b = new StringBuilder(); 
1952    b.append("\r\n"); 
1953    for (StackTraceElement t : e.getStackTrace()) { 
1954      b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 
1955      b.append("\r\n"); 
1956    } 
1957    return b.toString(); 
1958  } 
1959 
1960  protected String versionFromCanonical(String system) { 
1961    if (system == null) { 
1962      return null; 
1963    } else if (system.contains("|")) { 
1964      return system.substring(0, system.indexOf("|")); 
1965    } else { 
1966      return null; 
1967    } 
1968  } 
1969 
1970  protected String systemFromCanonical(String system) { 
1971    if (system == null) { 
1972      return null; 
1973    } else if (system.contains("|")) { 
1974      return system.substring(system.indexOf("|")+1); 
1975    } else { 
1976      return system; 
1977    } 
1978  } 
1979 
1980 
1981}