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.getChildNodes().addAll(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 "dateTime":
589    case "date" : 
590    case "instant" :
591      return displayDateTime(type);
592    default:
593      if (type.isPrimitive()) { 
594        return context.getTranslated(type); 
595      } else if (Utilities.existsInList(type.fhirType(),  "Meta", "Dosage", "Signature", "UsageContext", "RelatedArtifact", "ElementDefinition", "Base64BinaryType", "Attachment")) {
596        return "";
597      } else {
598        return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 
599      }
600    }
601  } 
602
603  private String displayAnnotation(ResourceWrapper type) {
604    return type.primitiveValue("text");
605  }
606
607  private String displayCodeableReference(ResourceWrapper type) {
608    if (type.has("reference")) {
609      return displayReference(type.child("reference"));
610    } else {
611      return displayCodeableConcept(type.child("concept"));
612    }
613  }
614
615
616  protected String displayReference(ResourceWrapper type) {
617    if (type.has("display")) {
618      return type.primitiveValue("display");
619    } else if (type.has("reference")) {
620      //      ResourceWithReference tr = resolveReference(res, r.getReference());
621      //      x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference()));
622      return "?ngen-3";
623    } else {
624      return "?ngen-4?";
625    }
626  }
627
628  private String displayRatio(ResourceWrapper type) {
629    return displayQuantity(type.child("numerator"))+" / "+displayQuantity(type.child("denominator"));
630  }
631
632  protected String displayDateTime(ResourceWrapper type) { 
633    if (!type.hasPrimitiveValue()) { 
634      return ""; 
635    } 
636
637    BaseDateTimeType t = new DateTimeType(type.primitiveValue());
638    // relevant inputs in rendering context: 
639    // timeZone, dateTimeFormat, locale, mode 
640    //   timezone - application specified timezone to use.  
641    //        null = default to the time of the date/time itself 
642    //   dateTimeFormat - application specified format for date times 
643    //        null = default to ... depends on mode 
644    //   mode - if rendering mode is technical, format defaults to XML format 
645    //   locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale)   
646    if (isOnlyDate(t.getPrecision())) { 
647
648      DateTimeFormatter fmt = getDateFormatForPrecision(t);       
649      LocalDate date = LocalDate.of(t.getYear(), t.getMonth()+1, t.getDay()); 
650      return fmt.format(date); 
651    } 
652
653    DateTimeFormatter fmt = context.getDateTimeFormat(); 
654    if (fmt == null) { 
655      if (context.isTechnicalMode()) { 
656        fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 
657      } else { 
658        fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 
659      } 
660    } 
661    ZonedDateTime zdt = ZonedDateTime.parse(t.primitiveValue()); 
662    ZoneId zone = context.getTimeZoneId(); 
663    if (zone != null) { 
664      zdt = zdt.withZoneSameInstant(zone); 
665    } 
666    return fmt.format(zdt); 
667  } 
668
669  private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 
670    DateTimeFormatter fmt = getContextDateFormat(type); 
671    if (fmt != null) { 
672      return fmt; 
673    } 
674    if (context.isTechnicalMode()) { 
675      switch (type.getPrecision()) { 
676      case YEAR: 
677        return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 
678      case MONTH: 
679        return  new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 
680      default: 
681        return DateTimeFormatter.ISO_DATE; 
682      } 
683    } else { 
684      switch (type.getPrecision()) { 
685      case YEAR: 
686        return DateTimeFormatter.ofPattern("uuuu"); 
687      case MONTH: 
688        return DateTimeFormatter.ofPattern("MMM uuuu"); 
689      default: 
690        return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 
691      } 
692    } 
693  } 
694
695  private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 
696    switch (type.getPrecision()) { 
697    case YEAR: 
698      return context.getDateYearFormat(); 
699    case MONTH: 
700      return context.getDateYearMonthFormat(); 
701    default: 
702      return context.getDateFormat(); 
703    } 
704  }    
705
706  private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 
707    return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 
708  } 
709
710
711  //  public void renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException  { 
712  ////    Base base = null; 
713  ////    try { 
714  ////      base = type.getBase(); 
715  ////    } catch (FHIRException | IOException e) { 
716  ////      x.tx(context.formatPhrase(RenderingContext.DATA_REND_ERROR, e.getMessage()) + " "); // this shouldn't happen - it's an error in the library itself 
717  ////      return; 
718  ////    } 
719  ////    if (base instanceof DataType) { 
720  ////      render(x, (DataType) base); 
721  ////    } else { 
722  ////      x.tx(context.formatPhrase(RenderingContext.DATA_REND_TO_DO, base.fhirType())); 
723  ////    } 
724  //  } 
725
726  public void renderBase(RenderingStatus status, XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 
727    if (b instanceof DataType) { 
728      renderDataType(status, x, wrapNC((DataType) b)); 
729    } else { 
730      x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " ");       
731    } 
732  } 
733  
734  public boolean canRenderDataType(String type) {
735    return context.getContextUtilities().isPrimitiveType(type) ||  Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept",  "Identifier", "HumanName", "Address",
736          "Expression",  "Money", "ContactPoint",  "Quantity",  "Range",  "Period", "Timing", "SampledData",  "Reference", "UsageContext",  "ContactDetail",  "Ratio",  "Attachment",  "CodeableReference");
737  }
738
739  public boolean renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
740    return renderDataType(status, null, x, type);
741  }
742  public boolean renderDataType(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 
743    switch (type.fhirType()) {
744    case "dateTime":
745    case "date" : 
746    case "instant" :
747      renderDateTime(status, x, type);
748      break;
749    case "uri" :
750    case "url" :
751      renderUri(status, x, type); 
752      break;
753    case "canonical" :
754      renderCanonical(status, x, type); 
755      break;
756    case "Annotation": 
757      renderAnnotation(status, x, type); 
758      break;
759    case "Coding": 
760      renderCodingWithDetails(status, x, type); 
761      break;
762    case "CodeableConcept": 
763      renderCodeableConcept(status, x, type); 
764      break;
765    case "Identifier": 
766      renderIdentifier(status, x, type); 
767      break;
768    case "HumanName": 
769      renderHumanName(status, x, type); 
770      break;
771    case "Address": 
772      renderAddress(status, x, type); 
773      break;
774    case "Expression": 
775      renderExpression(status, x, type); 
776      break;
777    case "Money": 
778      renderMoney(status, x, type); 
779      break;
780    case "ContactPoint": 
781      renderContactPoint(status, x, type); 
782      break;
783    case "Quantity": 
784      renderQuantity(status, x, type); 
785      break;
786    case "Range": 
787      renderRange(status, x, type); 
788      break;
789    case "Period": 
790      renderPeriod(status, x, type); 
791      break;
792    case "Timing": 
793      renderTiming(status, x, type); 
794      break;
795    case "SampledData": 
796      renderSampledData(status, x, type); 
797      break;
798    case "Reference": 
799      renderReference(status, x, type); 
800      break;
801    case "UsageContext": 
802      renderUsageContext(status, x, type); 
803      break;
804    case "ContactDetail": 
805      renderContactDetail(status, x, type); 
806      break;
807    case "Ratio": 
808      renderRatio(status, x, type); 
809      break;
810    case "Attachment": 
811      renderAttachment(status, x, type); 
812      break;
813    case "CodeableReference": 
814      if (type.has("concept")) { 
815        renderCodeableConcept(status, x, type.child("concept")); 
816      } else {  
817        renderReference(status, x, type.child("reference")); 
818      } 
819      break;
820    case "code": 
821      x.tx(getTranslatedCode(type)); 
822      break;
823    case "markdown": 
824      addMarkdown(parent == null ? x : parent, context.getTranslated(type)); // note parent not focus, because of paragraph issues and markdown 
825      break;
826    case "base64Binary":
827      int length = type.primitiveValue().length();
828      if (length >= context.getBase64Limit()) {
829        x.tx(context.formatPhrase(RenderingContext.DATA_REND_BASE64, length));
830      } else {
831        x.code(type.primitiveValue());
832      }
833      break;
834    default:
835      if (type.isPrimitive()) { 
836        if (!renderPrimitiveWithNoValue(status, x, type)) {
837          x.tx(context.getTranslated(type));
838        }
839      } else { 
840        x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 
841        return false;
842      }
843    } 
844    return true;
845  } 
846
847  // overide in ResourceRenderer
848  protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
849    renderUri(status, x, type);
850  }
851
852  private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) {
853    renderQuantity(status, x, type.child("numerator"));
854    x.tx("/");
855    renderQuantity(status, x, type.child("denominator"));
856  }
857
858  private void renderAttachment(RenderingStatus status, XhtmlNode x, ResourceWrapper att) {
859    String ct = att.primitiveValue("contentType");
860    if (att.has("url")) {
861      x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_URL, ct, att.primitiveValue("url")));
862    } else if (att.has("data")) {
863      x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_DATA, ct, displayDataType(att.child("data"))));      
864    }    
865  }
866
867  private void renderContactDetail(RenderingStatus status, XhtmlNode x, ResourceWrapper cd) {
868    if (cd.has("name")) {
869      x.tx(cd.primitiveValue("name")+": ");
870    }
871    boolean first = true;
872    for (ResourceWrapper c : cd.children("telecom")) {
873      if (first) first = false; else x.tx(",");
874      renderContactPoint(status, x, c);
875    }
876  }
877
878  private void renderDateTime(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
879    if (!renderPrimitiveWithNoValue(status, x, type)) {
880      x.tx(displayDateTime(type));
881    }
882  }
883
884  /** 
885   * this is overriden in ResourceRenderer where a better rendering is performed
886   * @param status
887   * @param x
888   * @param ref
889   * @throws IOException 
890   * @throws DefinitionException 
891   * @throws FHIRFormatError 
892   */
893  protected void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper ref) throws FHIRFormatError, DefinitionException, IOException { 
894    if (ref.has("display")) { 
895      x.tx(context.getTranslated(ref.child("display"))); 
896    } else if (ref.has("reference")) { 
897      x.tx(ref.primitiveValue("reference")); 
898    } else { 
899      x.tx("??"); 
900    } 
901  } 
902  // 
903  //  public void renderDateTime(RenderingStatus status, XhtmlNode x, Base e) { 
904  //    if (e.hasPrimitiveValue()) { 
905  //      x.addText(displayDateTime((DateTimeType) e)); 
906  //    } 
907  //  } 
908  // 
909  //  public void renderDate(RenderingStatus status, XhtmlNode x, Base e) { 
910  //    if (e.hasPrimitiveValue()) { 
911  //      x.addText(displayDateTime((DateType) e)); 
912  //    } 
913  //  } 
914  // 
915  //  public void renderDateTime(XhtmlNode x, String s) { 
916  //    if (s != null) { 
917  //      DateTimeType dt = new DateTimeType(s); 
918  //      x.addText(displayDateTime(dt)); 
919  //    } 
920  //  } 
921
922
923  protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, PrimitiveType<?> prim) throws FHIRFormatError, DefinitionException, IOException {
924    if (prim.hasPrimitiveValue()) {
925      return false;
926    }
927    boolean first = true;
928    for (Extension ext : prim.getExtension()) {
929      if (first) first = false; else x.tx(", ");
930      String url = ext.getUrl();
931      if (url.equals(ToolingExtensions.EXT_DAR)) {
932        x.tx("Absent because : ");
933        displayCode(x, wrapNC(ext.getValue()));
934      } else if (url.equals(ToolingExtensions.EXT_NF)) {
935        x.tx("Null because: ");
936        displayCode(x, wrapNC(ext.getValue()));
937      } else if (url.equals(ToolingExtensions.EXT_OT)) {
938        x.code().tx("Text: ");
939        displayCode(x, wrapNC(ext.getValue()));
940      } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) {
941        x.code().tx("Value calculated by: ");
942        renderExpression(status, x, wrapNC(ext.getValue()));
943      } else {
944        StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url);
945        if (def == null) {
946          x.code().tx(tail(url)+": ");
947        } else {
948          x.code().tx(def.present()+": ");
949        }
950        renderDataType(status, x, wrapNC(ext.getValue()));
951      }
952    }
953    status.setExtensions(true);
954    return true;
955  }
956
957  protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, ResourceWrapper prim) throws FHIRFormatError, DefinitionException, IOException {
958    if (prim.hasPrimitiveValue()) {
959      return false;
960    }
961    boolean first = true;
962    for (ResourceWrapper ext : prim.extensions()) {
963      if (first) first = false; else x.tx(", ");
964      String url = ext.primitiveValue("url");
965      if (url.equals(ToolingExtensions.EXT_DAR)) {
966        x.tx("Absent because : ");
967        displayCode(x, ext.child("value"));
968      } else if (url.equals(ToolingExtensions.EXT_NF)) {
969        x.tx("Null because: ");
970        displayCode(x, ext.child("value"));
971      } else if (url.equals(ToolingExtensions.EXT_OT)) {
972        x.code().tx("Text: ");
973        displayCode(x, ext.child("value"));
974      } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) {
975        x.code().tx("Value calculated by: ");
976        renderExpression(status, x, ext.child("value"));
977      } else {
978        StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url);
979        if (def == null) {
980          x.code().tx(tail(url)+": ");
981        } else {
982          x.code().tx(def.present()+": ");
983        }
984        renderDataType(status, x, ext.child("value"));
985      }
986    }
987    status.setExtensions(true);
988    return true;
989  }
990
991  private String tail(String url) {
992    return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url;
993  }
994
995  private void displayCode(XhtmlNode x, ResourceWrapper code) {
996    x.tx(code.primitiveValue());
997  }
998
999  protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 
1000    if (!renderPrimitiveWithNoValue(status, x, uri)) {
1001      String v = uri.primitiveValue();
1002
1003      if (context.getContextUtilities().isResource(v)) {
1004        v = "http://hl7.org/fhir/"+v;
1005      }
1006      if (v.startsWith("mailto:")) { 
1007        x.ah(v).addText(v.substring(7)); 
1008      } else { 
1009        Resource r = context.getContext().fetchResource(Resource.class, v); 
1010        if (r != null && r.getWebPath() != null) { 
1011          if (r instanceof CanonicalResource) { 
1012            x.ah(context.prefixLocalHref(r.getWebPath())).addText(crPresent((CanonicalResource) r));           
1013          } else { 
1014            x.ah(context.prefixLocalHref(r.getWebPath())).addText(v);           
1015          } 
1016        } else { 
1017          String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 
1018          if (url != null) {           
1019            x.ah(context.prefixLocalHref(url)).addText(v); 
1020          } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 
1021            x.ah(context.prefixLocalHref(v)).addText(v); 
1022          } else { 
1023            x.addText(v); 
1024          } 
1025        } 
1026      } 
1027    }
1028  } 
1029  
1030  protected void renderAnnotation(RenderingStatus status, XhtmlNode x, ResourceWrapper a) throws FHIRException { 
1031    StringBuilder b = new StringBuilder(); 
1032    if (a.has("text")) { 
1033      b.append(context.getTranslated(a.child("text"))); 
1034    } 
1035
1036    if (a.has("text") && (a.has("author") || a.has("time"))) { 
1037      b.append(" ("); 
1038    } 
1039
1040    if (a.has("author")) { 
1041      b.append(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 
1042      ResourceWrapper auth = a.child("author");
1043      if (auth.fhirType().equals("Reference")) { 
1044        b.append(auth.primitiveValue("reference")); 
1045      } else if (auth.fhirType().equals("string")) { 
1046        b.append(context.getTranslated(auth)); 
1047      } 
1048    } 
1049
1050
1051    if (a.has("time")) { 
1052      if (b.length() > 0) { 
1053        b.append(" "); 
1054      } 
1055      b.append("@").append(displayDateTime(a.child("time"))); 
1056    } 
1057    if (a.has("text") && (a.has("author") || a.has("time"))) { 
1058      b.append(")"); 
1059    } 
1060
1061
1062    x.addText(b.toString()); 
1063  } 
1064
1065  public String displayCoding(ResourceWrapper c) { 
1066    String s = ""; 
1067    if (context.isTechnicalMode()) { 
1068      s = context.getTranslated(c.child("display")); 
1069      if (Utilities.noString(s)) { 
1070        s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));         
1071      } 
1072      if (Utilities.noString(s)) { 
1073        s = displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1074      } else if (c.has("system")) { 
1075        s = s + " ("+displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"))+")"; 
1076      } else if (c.has("code")) { 
1077        s = s + " ("+c.primitiveValue("code")+")"; 
1078      } 
1079    } else { 
1080      if (c.has("display")) 
1081        return context.getTranslated(c.child("display")); 
1082      if (Utilities.noString(s)) 
1083        s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1084      if (Utilities.noString(s)) 
1085        s = c.primitiveValue("code"); 
1086    } 
1087    return s; 
1088  } 
1089
1090  private String displayCodeSource(String system, String version) { 
1091    String s = displaySystem(system); 
1092    if (version != null) { 
1093      s = s + "["+describeVersion(version)+"]"; 
1094    } 
1095    return s;     
1096  } 
1097
1098  private String displayCodeTriple(String system, String version, String code) { 
1099    if (system == null) { 
1100      if (code == null) { 
1101        return ""; 
1102      } else { 
1103        return "#"+code; 
1104      } 
1105    } else { 
1106      String s = displayCodeSource(system, version); 
1107      if (code != null) { 
1108        s = s + "#"+code; 
1109      } 
1110      return s; 
1111    } 
1112  } 
1113
1114  public String displayCoding(List<Coding> list) { 
1115    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1116    for (Coding c : list) { 
1117      b.append(displayCoding(wrapNC(c))); 
1118    } 
1119    return b.toString(); 
1120  } 
1121
1122  protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 
1123    if (c.isEmpty()) { 
1124      return; 
1125    } 
1126
1127    String url = getLinkForSystem(c.getSystem(), c.getVersion()); 
1128    String name = displayCodeSource(c.getSystem(), c.getVersion()); 
1129    if (!Utilities.noString(url)) { 
1130      pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
1131    } else {  
1132      pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 
1133    } 
1134    pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 
1135    String s = context.getTranslated(c.getDisplayElement()); 
1136    if (Utilities.noString(s)) { 
1137      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 
1138    } 
1139    if (!Utilities.noString(s)) { 
1140      pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 
1141    } 
1142  } 
1143
1144  protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper c) { 
1145    if (c.isEmpty()) { 
1146      return; 
1147    } 
1148
1149    String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 
1150    String name = displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version")); 
1151    if (!Utilities.noString(url)) { 
1152      pieces.add(gen.new Piece(url, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 
1153    } else {  
1154      pieces.add(gen.new Piece(null, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 
1155    } 
1156    pieces.add(gen.new Piece(null, "#"+c.primitiveValue("code"), null)); 
1157    String s = context.getTranslated(c.child("display")); 
1158    if (Utilities.noString(s)) { 
1159      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1160    } 
1161    if (!Utilities.noString(s)) { 
1162      pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 
1163    } 
1164  } 
1165
1166  private String getLinkForSystem(String system, String version) { 
1167    if ("http://snomed.info/sct".equals(system)) { 
1168      return "https://browser.ihtsdotools.org/";       
1169    } else if ("http://loinc.org".equals(system)) { 
1170      return "https://loinc.org/";             
1171    } else if ("http://unitsofmeasure.org".equals(system)) { 
1172      return "http://ucum.org";             
1173    } else { 
1174      String url = system; 
1175      if (version != null) { 
1176        url = url + "|"+version; 
1177      } 
1178      CodeSystem cs = context.getWorker().fetchCodeSystem(url); 
1179      if (cs != null && cs.hasWebPath()) { 
1180        return cs.getWebPath(); 
1181      } 
1182      return null; 
1183    } 
1184  } 
1185
1186  protected String getLinkForCode(String system, String version, String code) { 
1187    if ("http://snomed.info/sct".equals(system)) { 
1188      if (!Utilities.noString(code)) { 
1189        return "http://snomed.info/id/"+code;         
1190      } else { 
1191        return "https://browser.ihtsdotools.org/"; 
1192      } 
1193    } else if ("http://loinc.org".equals(system)) { 
1194      if (!Utilities.noString(code)) { 
1195        return "https://loinc.org/"+code; 
1196      } else { 
1197        return "https://loinc.org/"; 
1198      } 
1199    } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 
1200      if (!Utilities.noString(code)) { 
1201        return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code;         
1202      } else { 
1203        return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 
1204      } 
1205    } else if ("urn:iso:std:iso:3166".equals(system)) { 
1206      if (!Utilities.noString(code)) { 
1207        return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code;         
1208      } else { 
1209        return "https://en.wikipedia.org/wiki/ISO_3166-2"; 
1210      } 
1211    } else { 
1212      CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 
1213      if (cs != null && cs.hasWebPath()) { 
1214        if (!Utilities.noString(code)) { 
1215          return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 
1216        } else { 
1217          return cs.getWebPath(); 
1218        } 
1219      } 
1220    }   
1221    return null; 
1222  } 
1223
1224  public CodeResolution resolveCode(String system, String code) { 
1225    return resolveCode(new Coding().setSystem(system).setCode(code)); 
1226  } 
1227
1228  public CodeResolution resolveCode(ResourceWrapper c) { 
1229    String systemName; 
1230    String systemLink; 
1231    String link; 
1232    String display = null; 
1233    String hint; 
1234
1235    if (c.has("display")) 
1236      display = context.getTranslated(c.child("display")); 
1237    if (Utilities.noString(display)) 
1238      display = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1239    if (Utilities.noString(display)) { 
1240      display = c.primitiveValue("code"); 
1241    } 
1242
1243    CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 
1244    systemLink = cs != null ? cs.getWebPath() : null; 
1245    systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 
1246    link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1247
1248    hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 
1249    return new CodeResolution(systemName, systemLink, link, display, hint); 
1250  } 
1251
1252  public CodeResolution resolveCode(Coding code) {
1253    return resolveCode(wrapNC(code));
1254  }
1255
1256  public CodeResolution resolveCode(CodeableConcept code) { 
1257    if (code.hasCoding()) { 
1258      return resolveCode(code.getCodingFirstRep()); 
1259    } else { 
1260      return new CodeResolution(null, null, null, code.getText(), code.getText()); 
1261    } 
1262  } 
1263  protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 
1264    String s = ""; 
1265    if (c.has("display")) 
1266      s = context.getTranslated(c.child("display")); 
1267    if (Utilities.noString(s)) 
1268      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1269
1270    CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 
1271
1272    String sn = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 
1273    String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1274    if (link != null) { 
1275      x.ah(context.prefixLocalHref(link)).tx(sn); 
1276    } else { 
1277      x.tx(sn); 
1278    } 
1279
1280    x.tx(" "); 
1281    x.tx(c.primitiveValue("code")); 
1282    if (!Utilities.noString(s)) { 
1283      x.tx(": "); 
1284      x.tx(s); 
1285    } 
1286    if (c.has("version")) { 
1287      x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 
1288    } 
1289  } 
1290
1291  protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 
1292    String s = ""; 
1293    if (c.has("display")) 
1294      s = context.getTranslated(c.child("display")); 
1295    if (Utilities.noString(s)) 
1296      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1297
1298    if (Utilities.noString(s)) 
1299      s = c.primitiveValue("code"); 
1300
1301    if (context.isTechnicalMode()) { 
1302      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"), "')")); 
1303    } else 
1304      x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); 
1305  } 
1306
1307  public String displayCodeableConcept(ResourceWrapper cc) { 
1308    String s = context.getTranslated(cc.child("Text")); 
1309    if (Utilities.noString(s)) { 
1310      for (ResourceWrapper c : cc.children("coding")) { 
1311        if (c.has("display")) { 
1312          s = context.getTranslated(c.child("display")); 
1313          break; 
1314        } 
1315      } 
1316    } 
1317    if (Utilities.noString(s)) { 
1318      // still? ok, let's try looking it up 
1319      for (ResourceWrapper c : cc.children("coding")) { 
1320        if (c.has("code") && c.has("system")) { 
1321          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1322          if (!Utilities.noString(s)) 
1323            break; 
1324        } 
1325      } 
1326    } 
1327
1328    if (Utilities.noString(s)) { 
1329      if (!cc.has("coding")) 
1330        s = ""; 
1331      else 
1332        s = cc.children("coding").get(0).primitiveValue("code"); 
1333    } 
1334    return s; 
1335  } 
1336
1337
1338  protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 
1339    if (e.has("concept")) { 
1340      renderCodeableConcept(status, x, e.child("concept")); 
1341    } 
1342    if (e.has("reference")) { 
1343      renderReference(status, x, e.child("reference")); 
1344    } 
1345  } 
1346
1347  protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 
1348    if (cc.isEmpty()) { 
1349      return; 
1350    } 
1351
1352    String s = context.getTranslated(cc.child("text")); 
1353    if (Utilities.noString(s)) { 
1354      for (ResourceWrapper c : cc.children("coding")) { 
1355        if (c.has("display")) { 
1356          s = context.getTranslated(c.child("display")); 
1357          break; 
1358        } 
1359      } 
1360    } 
1361    if (Utilities.noString(s)) { 
1362      // still? ok, let's try looking it up 
1363      for (ResourceWrapper c : cc.children("coding")) { 
1364        if (c.has("code") && c.has("system")) { 
1365          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1366          if (!Utilities.noString(s)) 
1367            break; 
1368        } 
1369      } 
1370    } 
1371
1372    if (Utilities.noString(s)) { 
1373      if (!cc.has("coding")) 
1374        s = ""; 
1375      else 
1376        s = cc.children("coding").get(0).primitiveValue("code"); 
1377    } 
1378
1379    if (status.isShowCodeDetails()) { 
1380      x.addText(s+" "); 
1381      XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 
1382      sp.tx(" ("); 
1383      boolean first = true; 
1384      for (ResourceWrapper c : cc.children("coding")) { 
1385        if (first) { 
1386          first = false; 
1387        } else { 
1388          sp.tx("; "); 
1389        } 
1390        String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 
1391        if (url != null) { 
1392          sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1393        } else { 
1394          sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1395        } 
1396        if (c.has("code")) { 
1397          sp.tx("#"+c.primitiveValue("code")); 
1398        } 
1399        if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 
1400          sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 
1401        } 
1402      } 
1403      if (hasRenderableExtensions(cc)) { 
1404        if (!first) { 
1405          sp.tx("; "); 
1406        } 
1407        renderExtensionsInText(status, sp, cc, ";"); 
1408      } 
1409      sp.tx(")"); 
1410    } else { 
1411
1412      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1413      for (ResourceWrapper c : cc.children("coding")) { 
1414        if (c.has("code") && c.has("system")) { 
1415          b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 
1416        } 
1417      } 
1418
1419      x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); 
1420    } 
1421  } 
1422
1423  protected String displayIdentifier(ResourceWrapper ii) { 
1424    String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 
1425    if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 
1426      s = "OID:"+s.substring(8); 
1427    } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 
1428      s = "UUID:"+s.substring(9); 
1429    } else {  
1430      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1431      if (ns != null) { 
1432        s = crPresent(ns)+"#"+s; 
1433      } 
1434      if (ii.has("type")) { 
1435        ResourceWrapper type = ii.child("type");
1436        if (type.has("text")) 
1437          s = context.getTranslated(type.child("text"))+":\u00A0"+s; 
1438        else if (type.has("coding") && type.children("coding").get(0).has("display")) 
1439          s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 
1440        else if (type.has("coding") && type.children("coding").get(0).has("code")) 
1441          s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 
1442      } else if (ii.has("system")) { 
1443        s = ii.primitiveValue("system")+"#"+s; 
1444      } 
1445    } 
1446
1447    if (ii.has("use") || ii.has("period")) { 
1448      s = s + "\u00A0("; 
1449      if (ii.has("use")) { 
1450        s = s + "use:\u00A0"+ii.primitiveValue("use"); 
1451      } 
1452      if (ii.has("use") || ii.has("period")) { 
1453        s = s + ",\u00A0"; 
1454      } 
1455      if (ii.has("period")) { 
1456        s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 
1457      } 
1458      s = s + ")"; 
1459    }     
1460    return s; 
1461  } 
1462
1463  protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) {     
1464    if (ii.has("type")) { 
1465      ResourceWrapper type = ii.child("type");
1466      if (type.has("text")) { 
1467        x.tx(context.getTranslated(type.child("text"))); 
1468      } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 
1469        x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 
1470      } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 
1471        x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 
1472      } 
1473      x.tx("/"); 
1474    } else if (ii.has("system")) { 
1475      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1476      if (ns != null) { 
1477        if (ns.hasWebPath()) { 
1478          x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns));         
1479        } else { 
1480          x.tx(crPresent(ns)); 
1481        } 
1482      } else { 
1483        switch (ii.primitiveValue("system")) { 
1484        case "urn:oid:2.51.1.3": 
1485          x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 
1486          break; 
1487        default: 
1488          x.code(ii.primitiveValue("system"));       
1489        } 
1490      } 
1491      x.tx("/"); 
1492    } 
1493    x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 
1494
1495    if (ii.has("use") || ii.has("period")) { 
1496      x.nbsp(); 
1497      x.tx("("); 
1498      if (ii.has("use")) { 
1499        x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 
1500        x.nbsp(); 
1501        x.tx(ii.primitiveValue("use")); 
1502      } 
1503      if (ii.has("use") || ii.has("period")) { 
1504        x.tx(","); 
1505        x.nbsp(); 
1506      } 
1507      if (ii.has("period")) { 
1508        x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 
1509        x.nbsp(); 
1510        x.tx(displayPeriod(ii.child("period"))); 
1511      } 
1512      x.tx(")"); 
1513    }         
1514  } 
1515
1516  public static String displayHumanName(ResourceWrapper name) { 
1517    StringBuilder s = new StringBuilder(); 
1518    if (name.has("text")) 
1519      s.append(name.primitiveValue("text")); 
1520    else { 
1521      for (ResourceWrapper p : name.children("given")) { 
1522        s.append(p.primitiveValue()); 
1523        s.append(" "); 
1524      } 
1525      if (name.has("family")) { 
1526        s.append(name.primitiveValue("family")); 
1527        s.append(" "); 
1528      } 
1529    } 
1530    if (name.has("use") && !name.primitiveValue("use").equals("usual")) 
1531      s.append("("+name.primitiveValue("use")+")"); 
1532    return s.toString(); 
1533  } 
1534
1535
1536  protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) { 
1537    StringBuilder s = new StringBuilder(); 
1538    if (name.has("text")) 
1539      s.append(context.getTranslated(name.child("text"))); 
1540    else { 
1541      for (ResourceWrapper p : name.children("given")) { 
1542        s.append(context.getTranslated(p)); 
1543        s.append(" "); 
1544      } 
1545      if (name.has("family")) { 
1546        s.append(context.getTranslated(name.child("family"))); 
1547        s.append(" "); 
1548      } 
1549    } 
1550    if (name.has("use") && !name.primitiveValue("use").equals("usual")) { 
1551      s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")");
1552    }
1553    x.addText(s.toString()); 
1554  } 
1555
1556  private String displayAddress(ResourceWrapper address) { 
1557    StringBuilder s = new StringBuilder(); 
1558    if (address.has("text")) 
1559      s.append(context.getTranslated(address.child("text"))); 
1560    else { 
1561      for (ResourceWrapper p : address.children("line")) { 
1562        s.append(context.getTranslated(p)); 
1563        s.append(" "); 
1564      } 
1565      if (address.has("city")) { 
1566        s.append(context.getTranslated(address.child("city"))); 
1567        s.append(" "); 
1568      } 
1569      if (address.has("state")) { 
1570        s.append(context.getTranslated(address.child("state"))); 
1571        s.append(" "); 
1572      } 
1573
1574      if (address.has("postalCode")) { 
1575        s.append(context.getTranslated(address.child("postalCode"))); 
1576        s.append(" "); 
1577      } 
1578
1579      if (address.has("country")) { 
1580        s.append(context.getTranslated(address.child("country"))); 
1581        s.append(" "); 
1582      } 
1583    } 
1584    if (address.has("use")) {
1585      s.append("("+address.primitiveValue("use")+")");
1586    }
1587    return s.toString(); 
1588  } 
1589
1590  protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) { 
1591    x.addText(displayAddress(address)); 
1592  } 
1593
1594
1595  public String displayContactPoint(ResourceWrapper contact) { 
1596    StringBuilder s = new StringBuilder(); 
1597    s.append(describeSystem(contact.primitiveValue("system"))); 
1598    if (Utilities.noString(contact.primitiveValue("value"))) 
1599      s.append("-unknown-"); 
1600    else 
1601      s.append(contact.primitiveValue("value")); 
1602    if (contact.has("use")) 
1603      s.append("("+getTranslatedCode(contact.child("use"))+")"); 
1604    return s.toString(); 
1605  } 
1606
1607  public String displayContactDetail(ResourceWrapper contact) { 
1608    CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 
1609    for (ResourceWrapper cp : contact.children("telecom")) { 
1610      s.append(displayContactPoint(cp)); 
1611    } 
1612    return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 
1613  } 
1614
1615  protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 
1616    NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 
1617    numberFormat.setGroupingUsed(true); 
1618    numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 
1619    numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 
1620    return numberFormat.format(input); 
1621  } 
1622
1623  protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 
1624    if (x.getName().equals("blockquote")) { 
1625      x = x.para(); 
1626    } 
1627    Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 
1628    if (c != null) { 
1629      XhtmlNode s = x.span(null, c.getDisplayName()); 
1630      s.tx(c.getSymbol(context.getLocale())); 
1631      s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 
1632      x.tx(" ("+c.getCurrencyCode()+")"); 
1633    } else { 
1634      if (money.has("currency")) { 
1635        x.tx(money.primitiveValue("currency")); 
1636      } 
1637      x.tx(money.primitiveValue("value")); 
1638    } 
1639  } 
1640
1641  protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 
1642    // there's two parts: what the expression is, and how it's described.  
1643    // we start with what it is, and then how it's described  
1644    XhtmlNode p = x; 
1645    if (p.getName().equals("blockquote")) { 
1646      p = p.para(); 
1647    } 
1648    if (expr.has("expression")) { 
1649      if (expr.has("reference")) { 
1650        p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference")));         
1651      } 
1652      XhtmlNode c = p; 
1653      if (expr.has("language")) { 
1654        c = c.span(null, expr.primitiveValue("language")); 
1655      } 
1656      c.code().tx(expr.primitiveValue("expression")); 
1657    } else if (expr.has("reference")) { 
1658      p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 
1659    } 
1660    if (expr.has("name") || expr.has("description")) { 
1661      p.tx("("); 
1662      if (expr.has("name")) { 
1663        p.b().tx(expr.primitiveValue("name")); 
1664      } 
1665      if (expr.has("description")) { 
1666        p.tx("\""); 
1667        p.tx(context.getTranslated(expr.child("description"))); 
1668        p.tx("\""); 
1669      } 
1670      p.tx(")"); 
1671    } 
1672  } 
1673
1674
1675  protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 
1676    if (contact != null) { 
1677      if (!contact.has("system")) { 
1678        x.addText(displayContactPoint(contact));         
1679      } else { 
1680        String v = contact.primitiveValue("value");
1681        switch (contact.primitiveValue("system")) { 
1682        case "email": 
1683          x.ah("mailto:"+v).tx(v); 
1684          break; 
1685        case "fax": 
1686          x.addText(displayContactPoint(contact)); 
1687          break; 
1688        case "other": 
1689          x.addText(displayContactPoint(contact)); 
1690          break; 
1691        case "pager": 
1692          x.addText(displayContactPoint(contact)); 
1693          break; 
1694        case "phone": 
1695          if (contact.has("value") && v != null && v.startsWith("+")) { 
1696            x.ah("tel:"+v.replace(" ", "")).tx(v); 
1697          } else { 
1698            x.addText(displayContactPoint(contact)); 
1699          } 
1700          break; 
1701        case "sms": 
1702          x.addText(displayContactPoint(contact)); 
1703          break; 
1704        case "url": 
1705          x.ah(context.prefixLocalHref(v)).tx(v); 
1706          break; 
1707        default: 
1708          break;       
1709        } 
1710      } 
1711    } 
1712  } 
1713
1714  protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 
1715    if (c != null) { 
1716      if (c.getSystem() == ContactPointSystem.PHONE) { 
1717        p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 
1718      } else if (c.getSystem() == ContactPointSystem.FAX) { 
1719        p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 
1720      } else if (c.getSystem() == ContactPointSystem.EMAIL) { 
1721        p.tx(c.getValue()); 
1722      } else if (c.getSystem() == ContactPointSystem.URL) { 
1723        if (c.getValue().length() > 30) { 
1724          p.addText(c.getValue().substring(0, 30)+"..."); 
1725        } else { 
1726          p.addText(c.getValue()); 
1727        } 
1728      } 
1729    } 
1730  } 
1731
1732  protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 
1733    String sys = c.primitiveValue("system");
1734    String value = c.primitiveValue("value");
1735    if (sys.equals("phone")) { 
1736      p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 
1737    } else if (sys.equals("fax")) { 
1738      p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 
1739    } else if (sys.equals("email")) { 
1740      p.ah("mailto:"+value).addText(value); 
1741    } else if (sys.equals("url")) { 
1742      if (value.length() > 30) 
1743        p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 
1744      else 
1745        p.ah(context.prefixLocalHref(value)).addText(value); 
1746    } 
1747  } 
1748  private static String describeSystem(String system) { 
1749    if (system == null) 
1750      return ""; 
1751    switch (system) { 
1752    case "phone": return "ph: "; 
1753    case "fax": return "fax: "; 
1754    default: 
1755      return ""; 
1756    } 
1757  } 
1758
1759  protected String displayQuantity(ResourceWrapper q) { 
1760    if (q == null) {
1761      return "";
1762    }
1763    StringBuilder s = new StringBuilder(); 
1764
1765    s.append(q.has("value") ? q.primitiveValue("value") : "?"); 
1766    if (q.has("unit")) 
1767      s.append(" ").append(q.primitiveValue("unit")); 
1768    else if (q.has("code")) 
1769      s.append(" ").append(q.primitiveValue("code")); 
1770
1771    return s.toString(); 
1772  }   
1773
1774  protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 
1775    if (q.has("comparator")) 
1776      x.addText(q.primitiveValue("comparator")); 
1777    if (q.has("value")) { 
1778      x.addText(context.getTranslated(q.child("value"))); 
1779    } 
1780    if (q.has("unit")) 
1781      x.tx(" "+context.getTranslated(q.child("unit"))); 
1782    else if (q.has("code") && q.has("system")) { 
1783      // 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 
1784      if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 
1785        x.tx(" "+q.primitiveValue("code")); 
1786      else 
1787        x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 
1788    } 
1789    if (context.isTechnicalMode() && q.has("code")) { 
1790      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"))+"')"); 
1791    } 
1792  } 
1793
1794
1795  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 
1796    pieces.add(gen.new Piece(null, displayQuantity(q), null)); 
1797  } 
1798
1799  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 
1800    pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 
1801  } 
1802
1803  public String displayRange(ResourceWrapper q) { 
1804    if (!q.has("low") && !q.has("high")) 
1805      return "?"; 
1806
1807    StringBuilder b = new StringBuilder(); 
1808
1809    ResourceWrapper lowC = q.child("low");
1810    ResourceWrapper highC = q.child("high");
1811    boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit")))  
1812        || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 
1813    String low = "?"; 
1814    if (q.has("low") && lowC.has("value")) 
1815      low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 
1816    String high = displayQuantity(highC); 
1817    if (high.isEmpty()) 
1818      high = "?"; 
1819    b.append(low).append("\u00A0to\u00A0").append(high); 
1820    return b.toString(); 
1821  } 
1822
1823  protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 
1824    if (q.has("low")) 
1825      x.addText(q.child("low").primitiveValue("value").toString()); 
1826    else 
1827      x.tx("?"); 
1828    x.tx("-"); 
1829    if (q.has("high")) 
1830      x.addText(q.child("high").primitiveValue("value").toString()); 
1831    else 
1832      x.tx("?"); 
1833    if (q.has("low") && q.child("low").has("unit")) 
1834      x.tx(" "+q.child("low").child("unit")); 
1835  } 
1836
1837  public String displayPeriod(ResourceWrapper p) { 
1838    String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 
1839    s = s + " --> "; 
1840    return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1841  } 
1842
1843  public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 
1844    x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 
1845    x.tx(" --> "); 
1846    x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1847  } 
1848
1849  public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 
1850    renderCoding(status, x, u.child("code")); 
1851    x.tx(": "); 
1852    renderDataType(status, x, u.child("value"));     
1853  } 
1854
1855
1856  public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 
1857    if (x.isPara()) { 
1858      x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1859      x.tx(": "); 
1860      x.tx(td.child("type").primitiveValue("display")); 
1861
1862      if (td.has("name")) {     
1863        x.tx(", "); 
1864        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1865        x.tx(": "); 
1866        x.tx(context.getTranslated(td.child("name"))); 
1867      } 
1868      if (td.has("code")) {     
1869        x.tx(", "); 
1870        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1871        x.tx(": "); 
1872        renderCodeableConcept(status, x, td.child("code")); 
1873      } 
1874      if (td.has("timing")) {     
1875        x.tx(", "); 
1876        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1877        x.tx(": "); 
1878        renderDataType(status, x, td.child("timing")); 
1879      } 
1880      if (td.has("condition")) {     
1881        x.tx(", "); 
1882        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1883        x.tx(": "); 
1884        renderExpression(status, x, td.child("condition")); 
1885      }     
1886    } else { 
1887      XhtmlNode tbl = x.table("grid"); 
1888
1889      XhtmlNode tr = tbl.tr();   
1890      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1891      tr.td().tx(td.child("type").primitiveValue("display")); 
1892
1893      if (td.has("name")) {     
1894        tr = tbl.tr();   
1895        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1896        tr.td().tx(context.getTranslated(td.child("name"))); 
1897      } 
1898      if (td.has("code")) {     
1899        tr = tbl.tr();   
1900        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1901        renderCodeableConcept(status, tr.td(), td.child("code")); 
1902      } 
1903      if (td.has("timing")) {     
1904        tr = tbl.tr();   
1905        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1906        renderDataType(status, tr.td(), td.child("timing")); 
1907      } 
1908      if (td.has("condition")) {      
1909        tr = tbl.tr();   
1910        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1911        renderExpression(status, tr.td(), td.child("condition")); 
1912      }     
1913    } 
1914  } 
1915
1916  public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 
1917    XhtmlNode tbl = x.table("grid"); 
1918    XhtmlNode tr = tbl.tr();     
1919    XhtmlNode td = tr.td().colspan("2"); 
1920    td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1921    td.tx(": "); 
1922    StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 
1923    if (sd != null && sd.hasWebPath()) { 
1924      td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 
1925    } else { 
1926      td.tx(dr.primitiveValue("type")); 
1927    } 
1928    if (dr.has("profile")) { 
1929      td.tx(" ("); 
1930      boolean first = true; 
1931      for (ResourceWrapper p : dr.children("profile")) { 
1932        if (first) first = false; else td.tx(" | "); 
1933        sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 
1934        if (sd != null && sd.hasWebPath()) { 
1935          td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 
1936        } else { 
1937          td.tx(p.primitiveValue()); 
1938        } 
1939      } 
1940      td.tx(")"); 
1941    } 
1942    if (dr.has("subject")) { 
1943      tr = tbl.tr();     
1944      td = tr.td().colspan("2"); 
1945      td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 
1946      ResourceWrapper subj = dr.child("subject");
1947      if (subj.fhirType().equals("reference")) { 
1948        renderReference(status, td, subj); 
1949      } else { 
1950        renderCodeableConcept(status, td, subj); 
1951      } 
1952    } 
1953    if (dr.has("codeFilter") || dr.has("dateFilter")) { 
1954      tr = tbl.tr().backgroundColor("#efefef");     
1955      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 
1956      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 
1957    } 
1958    for (ResourceWrapper cf : dr.children("codeFilter")) { 
1959      tr = tbl.tr();     
1960      if (cf.has("path")) { 
1961        tr.td().tx(cf.primitiveValue("path")); 
1962      } else { 
1963        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
1964      } 
1965      if (cf.has("valueSet")) { 
1966        td = tr.td(); 
1967        td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 
1968        renderDataType(status, td, cf.child("valueSet")); 
1969      } else { 
1970        boolean first = true; 
1971        td = tr.td(); 
1972        td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 
1973        for (ResourceWrapper c : cf.children("code")) { 
1974          if (first) first = false; else td.tx(", "); 
1975          renderDataType(status, td, c); 
1976        } 
1977      } 
1978    } 
1979    for (ResourceWrapper cf : dr.children("dateFilter")) { 
1980      tr = tbl.tr();     
1981      if (cf.has("path")) { 
1982        tr.td().tx(cf.primitiveValue("path")); 
1983      } else { 
1984        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
1985      } 
1986      renderDataType(status, tr.td(), cf.child("value")); 
1987    } 
1988    if (dr.has("sort") || dr.has("limit")) { 
1989      tr = tbl.tr();     
1990      td = tr.td().colspan("2"); 
1991      if (dr.has("limit")) { 
1992        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 
1993        td.tx(": "); 
1994        td.tx(dr.primitiveValue("limit")); 
1995        if (dr.has("sort")) { 
1996          td.tx(", "); 
1997        } 
1998      } 
1999      if (dr.has("sort")) { 
2000        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 
2001        td.tx(": "); 
2002        boolean first = true; 
2003        for (ResourceWrapper p : dr.children("sort")) { 
2004          if (first) first = false; else td.tx(" | "); 
2005          td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 
2006          td.tx(p.primitiveValue("path")); 
2007        } 
2008      } 
2009    } 
2010  } 
2011
2012
2013  private String displayTiming(ResourceWrapper s) throws FHIRException { 
2014    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2015    if (s.has("code")) {
2016      b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 
2017    }
2018
2019    if (s.has("event")) { 
2020      CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 
2021      for (ResourceWrapper p : s.children("event")) { 
2022        if (p.hasPrimitiveValue()) { 
2023          c.append(displayDateTime(p)); 
2024        } else if (!renderExpression(c, p)) { 
2025          c.append("??"); 
2026        }         
2027      } 
2028      b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 
2029    } 
2030
2031    if (s.has("repeat")) { 
2032      ResourceWrapper rep = s.child("repeat"); 
2033      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 
2034        b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 
2035      if (rep.has("count")) 
2036        b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 
2037      if (rep.has("duration")) 
2038        b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"))) + " "); 
2039
2040      if (rep.has("when")) { 
2041        String st = ""; 
2042        if (rep.has("offset")) { 
2043          st = rep.primitiveValue("offset")+"min "; 
2044        } 
2045        b.append(st); 
2046        for (ResourceWrapper wh : rep.children("when"))  {
2047          b.append(displayEventCode(wh.primitiveValue()));
2048        }
2049      } else { 
2050        String st = ""; 
2051        if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 
2052          st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 
2053        } else { 
2054          st = rep.primitiveValue("frequency"); 
2055          if (rep.has("frequencyMax")) 
2056            st = st + "-"+rep.primitiveValue("frequencyMax"); 
2057        } 
2058        if (rep.has("period")) { 
2059          st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+rep.primitiveValue("period"); 
2060          if (rep.has("periodMax")) 
2061            st = st + "-"+rep.primitiveValue("periodMax"); 
2062          st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit")); 
2063        } 
2064        b.append(st); 
2065      } 
2066      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) 
2067        b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 
2068    } 
2069    return b.toString(); 
2070  } 
2071
2072  private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 
2073    ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 
2074    if (exp == null || !exp.has("value")) { 
2075      return false; 
2076    } 
2077    c.append(exp.child("value").primitiveValue("expression")); 
2078    return true; 
2079  } 
2080
2081  private String displayEventCode(String when) { 
2082    switch (when) { 
2083    case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 
2084    case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 
2085    case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 
2086    case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 
2087    case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 
2088    case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 
2089    case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 
2090    case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 
2091    case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 
2092    case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 
2093    case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 
2094    case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 
2095    case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 
2096    case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 
2097    default: return "?ngen-6?"; 
2098    } 
2099  } 
2100
2101  private String displayTimeUnits(String units) { 
2102    if (units == null) 
2103      return "?ngen-7?"; 
2104    switch (units) { 
2105    case "a": return "years"; 
2106    case "d": return "days"; 
2107    case "h": return "hours"; 
2108    case "min": return "minutes"; 
2109    case "mo": return "months"; 
2110    case "s": return "seconds"; 
2111    case "wk": return "weeks"; 
2112    default: return "?ngen-8?"; 
2113    } 
2114  } 
2115
2116  protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 
2117    x.addText(displayTiming(s)); 
2118  } 
2119
2120
2121  private String displaySampledData(ResourceWrapper s) { 
2122    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2123    if (s.has("origin")) 
2124      b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 
2125
2126    if (s.has("interval")) { 
2127      b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 
2128
2129      if (s.has("intervalUnit")) 
2130        b.append(s.primitiveValue("intervalUnit")); 
2131    } 
2132
2133    if (s.has("factor")) 
2134      b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 
2135
2136    if (s.has("lowerLimit")) 
2137      b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 
2138
2139    if (s.has("upperLimit")) 
2140      b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 
2141
2142    if (s.has("dimensions")) 
2143      b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 
2144
2145    if (s.has("data")) 
2146      b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 
2147
2148    return b.toString(); 
2149  } 
2150
2151  protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 
2152    x.addText(displaySampledData(sampledData)); 
2153  } 
2154
2155  public RenderingContext getContext() { 
2156    return context; 
2157  } 
2158
2159
2160  public XhtmlNode makeExceptionXhtml(Exception e, String function) { 
2161    XhtmlNode xn; 
2162    xn = new XhtmlNode(NodeType.Element, "div"); 
2163    XhtmlNode p = xn.para(); 
2164    p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 
2165    p.addComment(getStackTrace(e)); 
2166    return xn; 
2167  } 
2168
2169  private String getStackTrace(Exception e) { 
2170    StringBuilder b = new StringBuilder(); 
2171    b.append("\r\n"); 
2172    for (StackTraceElement t : e.getStackTrace()) { 
2173      b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 
2174      b.append("\r\n"); 
2175    } 
2176    return b.toString(); 
2177  } 
2178
2179  protected String versionFromCanonical(String system) { 
2180    if (system == null) { 
2181      return null; 
2182    } else if (system.contains("|")) { 
2183      return system.substring(0, system.indexOf("|")); 
2184    } else { 
2185      return null; 
2186    } 
2187  } 
2188
2189  protected String systemFromCanonical(String system) { 
2190    if (system == null) { 
2191      return null; 
2192    } else if (system.contains("|")) { 
2193      return system.substring(system.indexOf("|")+1); 
2194    } else { 
2195      return system; 
2196    } 
2197  } 
2198
2199
2200}