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