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