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