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) { 
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); 
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) { 
1282    return resolveCode(new Coding().setSystem(system).setCode(code)); 
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    systemLink = cs != null ? cs.getWebPath() : null; 
1302    systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 
1303    link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1304
1305    hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 
1306    return new CodeResolution(systemName, systemLink, link, display, hint); 
1307  } 
1308
1309  public CodeResolution resolveCode(Coding code) {
1310    return resolveCode(wrapNC(code));
1311  }
1312
1313  public CodeResolution resolveCode(CodeableConcept code) { 
1314    if (code.hasCoding()) { 
1315      return resolveCode(code.getCodingFirstRep()); 
1316    } else { 
1317      return new CodeResolution(null, null, null, code.getText(), code.getText()); 
1318    } 
1319  } 
1320  protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { 
1321    String s = ""; 
1322    if (c.has("display")) 
1323      s = context.getTranslated(c.child("display")); 
1324    if (Utilities.noString(s)) 
1325      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1326
1327    String sn = displaySystem(c.primitiveValue("system"));
1328    String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));
1329    XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x;    
1330    xi.tx(sn);
1331    xi.tx(" "); 
1332
1333    xi.tx(c.primitiveValue("code"));
1334    
1335    if (!Utilities.noString(s)) { 
1336      x.tx(": "); 
1337      x.tx(s); 
1338    } 
1339    if (c.has("version")) { 
1340      x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 
1341    } 
1342    checkRenderExtensions(status, x, c);
1343  } 
1344
1345  protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) {
1346    renderCoding(status, x, c, true);
1347  }
1348    
1349  protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { 
1350    String s = ""; 
1351    if (c.has("display")) 
1352      s = context.getTranslated(c.child("display")); 
1353    if (Utilities.noString(s)) 
1354      s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1355
1356    if (Utilities.noString(s)) 
1357      s = c.primitiveValue("code"); 
1358
1359    if (context.isTechnicalMode() && details) {
1360      String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display");
1361      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); 
1362      x.addText(s+" "+d);
1363    } else { 
1364      x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s);
1365    }
1366  } 
1367
1368  public String displayCodeableConcept(ResourceWrapper cc) { 
1369    String s = context.getTranslated(cc.child("Text")); 
1370    if (Utilities.noString(s)) { 
1371      for (ResourceWrapper c : cc.children("coding")) { 
1372        if (c.has("display")) { 
1373          s = context.getTranslated(c.child("display")); 
1374          break; 
1375        } 
1376      } 
1377    } 
1378    if (Utilities.noString(s)) { 
1379      // still? ok, let's try looking it up 
1380      for (ResourceWrapper c : cc.children("coding")) { 
1381        if (c.has("code") && c.has("system")) { 
1382          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1383          if (!Utilities.noString(s)) 
1384            break; 
1385        } 
1386      } 
1387    } 
1388
1389    if (Utilities.noString(s)) { 
1390      if (!cc.has("coding")) 
1391        s = ""; 
1392      else 
1393        s = cc.children("coding").get(0).primitiveValue("code"); 
1394    } 
1395    return s; 
1396  } 
1397
1398
1399  protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 
1400    if (e.has("concept")) { 
1401      renderCodeableConcept(status, x, e.child("concept")); 
1402    } 
1403    if (e.has("reference")) { 
1404      renderReference(status, x, e.child("reference")); 
1405    } 
1406  } 
1407
1408  protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 
1409    if (cc.isEmpty()) { 
1410      return; 
1411    } 
1412
1413    String s = context.getTranslated(cc.child("text")); 
1414    if (Utilities.noString(s)) { 
1415      for (ResourceWrapper c : cc.children("coding")) { 
1416        if (c.has("display")) { 
1417          s = context.getTranslated(c.child("display")); 
1418          break; 
1419        } 
1420      } 
1421    } 
1422    if (Utilities.noString(s)) { 
1423      // still? ok, let's try looking it up 
1424      for (ResourceWrapper c : cc.children("coding")) { 
1425        if (c.has("code") && c.has("system")) { 
1426          s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 
1427          if (!Utilities.noString(s)) 
1428            break; 
1429        } 
1430      } 
1431    } 
1432
1433    if (Utilities.noString(s)) { 
1434      if (!cc.has("coding")) 
1435        s = ""; 
1436      else 
1437        s = cc.children("coding").get(0).primitiveValue("code"); 
1438    } 
1439
1440    if (status.isShowCodeDetails()) { 
1441      x.addTextWithLineBreaks(s+" "); 
1442      XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 
1443      sp.tx(" ("); 
1444      boolean first = true; 
1445      for (ResourceWrapper c : cc.children("coding")) { 
1446        if (first) { 
1447          first = false; 
1448        } else { 
1449          sp.tx("; "); 
1450        } 
1451        String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 
1452        if (url != null) { 
1453          sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1454        } else { 
1455          sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 
1456        } 
1457        if (c.has("code")) { 
1458          sp.tx("#"+c.primitiveValue("code")); 
1459        } 
1460        if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 
1461          sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 
1462        } 
1463      } 
1464      if (hasRenderableExtensions(cc)) { 
1465        if (!first) { 
1466          sp.tx("; "); 
1467        } 
1468        renderExtensionsInText(status, sp, cc, ";"); 
1469      } 
1470      sp.tx(")"); 
1471    } else { 
1472
1473      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
1474      for (ResourceWrapper c : cc.children("coding")) { 
1475        if (c.has("code") && c.has("system")) { 
1476          b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 
1477        } 
1478      } 
1479
1480      x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addTextWithLineBreaks(s); 
1481    }       
1482    checkRenderExtensions(status, x, cc);
1483  } 
1484
1485  protected String displayIdentifier(ResourceWrapper ii) { 
1486    String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 
1487    if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 
1488      s = "OID:"+s.substring(8); 
1489    } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 
1490      s = "UUID:"+s.substring(9); 
1491    } else {  
1492      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1493      if (ns != null) { 
1494        s = crPresent(ns)+"#"+s; 
1495      } 
1496      if (ii.has("type")) { 
1497        ResourceWrapper type = ii.child("type");
1498        if (type.has("text")) 
1499          s = context.getTranslated(type.child("text"))+":\u00A0"+s; 
1500        else if (type.has("coding") && type.children("coding").get(0).has("display")) 
1501          s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 
1502        else if (type.has("coding") && type.children("coding").get(0).has("code")) 
1503          s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 
1504      } else if (ii.has("system")) { 
1505        s = ii.primitiveValue("system")+"#"+s; 
1506      } 
1507    } 
1508
1509    if (ii.has("use") || ii.has("period")) { 
1510      s = s + "\u00A0("; 
1511      if (ii.has("use")) { 
1512        s = s + "use:\u00A0"+ii.primitiveValue("use"); 
1513      } 
1514      if (ii.has("use") || ii.has("period")) { 
1515        s = s + ",\u00A0"; 
1516      } 
1517      if (ii.has("period")) { 
1518        s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 
1519      } 
1520      s = s + ")"; 
1521    }     
1522    return s; 
1523  } 
1524
1525  protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException {     
1526    if (ii.has("type")) { 
1527      ResourceWrapper type = ii.child("type");
1528      if (type.has("text")) { 
1529        x.tx(context.getTranslated(type.child("text"))); 
1530      } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 
1531        x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 
1532      } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 
1533        x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 
1534      } 
1535      x.tx("/"); 
1536    } else if (ii.has("system")) { 
1537      NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 
1538      if (ns != null) { 
1539        if (ns.hasWebPath()) { 
1540          x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns));         
1541        } else { 
1542          x.tx(crPresent(ns)); 
1543        } 
1544      } else { 
1545        switch (ii.primitiveValue("system")) { 
1546        case "urn:oid:2.51.1.3": 
1547          x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 
1548          break; 
1549        default: 
1550          x.code(ii.primitiveValue("system"));       
1551        } 
1552      } 
1553      x.tx("/"); 
1554    } 
1555    x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 
1556
1557    if (ii.has("use") || ii.has("period")) { 
1558      x.nbsp(); 
1559      x.tx("("); 
1560      if (ii.has("use")) { 
1561        x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 
1562        x.nbsp(); 
1563        x.tx(ii.primitiveValue("use")); 
1564      } 
1565      if (ii.has("use") || ii.has("period")) { 
1566        x.tx(","); 
1567        x.nbsp(); 
1568      } 
1569      if (ii.has("period")) { 
1570        x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 
1571        x.nbsp(); 
1572        x.tx(displayPeriod(ii.child("period"))); 
1573      } 
1574      x.tx(")"); 
1575    }            
1576    checkRenderExtensions(status, x, ii);   
1577  } 
1578
1579  public static String displayHumanName(ResourceWrapper name) { 
1580    StringBuilder s = new StringBuilder(); 
1581    if (name.has("text")) 
1582      s.append(name.primitiveValue("text")); 
1583    else { 
1584      for (ResourceWrapper p : name.children("given")) { 
1585        s.append(p.primitiveValue()); 
1586        s.append(" "); 
1587      } 
1588      if (name.has("family")) { 
1589        s.append(name.primitiveValue("family")); 
1590        s.append(" "); 
1591      } 
1592    } 
1593    if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) 
1594      s.append("("+name.primitiveValue("use")+")"); 
1595    return s.toString(); 
1596  } 
1597
1598
1599  protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { 
1600    StringBuilder s = new StringBuilder(); 
1601    if (name.has("text")) 
1602      s.append(context.getTranslated(name.child("text"))); 
1603    else { 
1604      for (ResourceWrapper p : name.children("given")) { 
1605        s.append(context.getTranslated(p)); 
1606        s.append(" "); 
1607      } 
1608      if (name.has("family")) { 
1609        s.append(context.getTranslated(name.child("family"))); 
1610        s.append(" "); 
1611      } 
1612    } 
1613    if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) { 
1614      s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")");
1615    }
1616    x.addTextWithLineBreaks(s.toString());       
1617    checkRenderExtensions(status, x, name);
1618  } 
1619
1620  private String displayAddress(ResourceWrapper address) { 
1621    StringBuilder s = new StringBuilder(); 
1622    if (address.has("text")) 
1623      s.append(context.getTranslated(address.child("text"))); 
1624    else { 
1625      for (ResourceWrapper p : address.children("line")) { 
1626        s.append(context.getTranslated(p)); 
1627        s.append(" "); 
1628      } 
1629      if (address.has("city")) { 
1630        s.append(context.getTranslated(address.child("city"))); 
1631        s.append(" "); 
1632      } 
1633      if (address.has("state")) { 
1634        s.append(context.getTranslated(address.child("state"))); 
1635        s.append(" "); 
1636      } 
1637
1638      if (address.has("postalCode")) { 
1639        s.append(context.getTranslated(address.child("postalCode"))); 
1640        s.append(" "); 
1641      } 
1642
1643      if (address.has("country")) { 
1644        s.append(context.getTranslated(address.child("country"))); 
1645        s.append(" "); 
1646      } 
1647    } 
1648    if (address.has("use")) {
1649      s.append("("+address.primitiveValue("use")+")");
1650    }
1651    return s.toString(); 
1652  } 
1653
1654  protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { 
1655    x.addTextWithLineBreaks(displayAddress(address));       
1656    checkRenderExtensions(status, x, address);
1657  } 
1658
1659
1660  public String displayContactPoint(ResourceWrapper contact) { 
1661    StringBuilder s = new StringBuilder(); 
1662    s.append(describeSystem(contact.primitiveValue("system"))); 
1663    if (Utilities.noString(contact.primitiveValue("value"))) 
1664      s.append("-unknown-"); 
1665    else 
1666      s.append(contact.primitiveValue("value")); 
1667    if (contact.has("use")) 
1668      s.append("("+getTranslatedCode(contact.child("use"))+")"); 
1669    return s.toString(); 
1670  } 
1671
1672  public String displayContactDetail(ResourceWrapper contact) { 
1673    CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 
1674    for (ResourceWrapper cp : contact.children("telecom")) { 
1675      s.append(displayContactPoint(cp)); 
1676    } 
1677    return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 
1678  } 
1679
1680  protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 
1681    NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 
1682    numberFormat.setGroupingUsed(true); 
1683    numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 
1684    numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 
1685    return numberFormat.format(input); 
1686  } 
1687
1688  protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 
1689    if (x.getName().equals("blockquote")) { 
1690      x = x.para(); 
1691    } 
1692    Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 
1693    if (c != null) { 
1694      XhtmlNode s = x.span(null, c.getDisplayName()); 
1695      s.tx(c.getSymbol(context.getLocale())); 
1696      s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 
1697      x.tx(" ("+c.getCurrencyCode()+")"); 
1698    } else { 
1699      if (money.has("currency")) { 
1700        x.tx(money.primitiveValue("currency")); 
1701      } 
1702      x.tx(money.primitiveValue("value")); 
1703    } 
1704  } 
1705
1706  protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 
1707    // there's two parts: what the expression is, and how it's described.  
1708    // we start with what it is, and then how it's described  
1709    XhtmlNode p = x; 
1710    if (p.getName().equals("blockquote")) { 
1711      p = p.para(); 
1712    } 
1713    if (expr.has("expression")) { 
1714      if (expr.has("reference")) { 
1715        p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference")));         
1716      } 
1717      XhtmlNode c = p; 
1718      if (expr.has("language")) { 
1719        c = c.span(null, expr.primitiveValue("language")); 
1720      } 
1721      c.code().tx(expr.primitiveValue("expression")); 
1722    } else if (expr.has("reference")) { 
1723      p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 
1724    } 
1725    if (expr.has("name") || expr.has("description")) { 
1726      p.tx("("); 
1727      if (expr.has("name")) { 
1728        p.b().tx(expr.primitiveValue("name")); 
1729      } 
1730      if (expr.has("description")) { 
1731        p.tx("\""); 
1732        p.tx(context.getTranslated(expr.child("description"))); 
1733        p.tx("\""); 
1734      } 
1735      p.tx(")"); 
1736    } 
1737  } 
1738
1739
1740  protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 
1741    if (contact != null) { 
1742      if (!contact.has("system")) { 
1743        x.addText(displayContactPoint(contact));         
1744      } else { 
1745        String v = contact.primitiveValue("value");
1746        switch (contact.primitiveValue("system")) { 
1747        case "email": 
1748          x.ah("mailto:"+v).tx(v); 
1749          break; 
1750        case "fax": 
1751          x.addText(displayContactPoint(contact)); 
1752          break; 
1753        case "other": 
1754          x.addText(displayContactPoint(contact)); 
1755          break; 
1756        case "pager": 
1757          x.addText(displayContactPoint(contact)); 
1758          break; 
1759        case "phone": 
1760          if (contact.has("value") && v != null && v.startsWith("+")) { 
1761            x.ah("tel:"+v.replace(" ", "")).tx(v); 
1762          } else { 
1763            x.addText(displayContactPoint(contact)); 
1764          } 
1765          break; 
1766        case "sms": 
1767          x.addText(displayContactPoint(contact)); 
1768          break; 
1769        case "url": 
1770          x.ah(context.prefixLocalHref(v)).tx(v); 
1771          break; 
1772        default: 
1773          break;       
1774        } 
1775      } 
1776    } 
1777  } 
1778
1779  protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 
1780    if (c != null) { 
1781      if (c.getSystem() == ContactPointSystem.PHONE) { 
1782        p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 
1783      } else if (c.getSystem() == ContactPointSystem.FAX) { 
1784        p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 
1785      } else if (c.getSystem() == ContactPointSystem.EMAIL) { 
1786        p.tx(c.getValue()); 
1787      } else if (c.getSystem() == ContactPointSystem.URL) { 
1788        if (c.getValue().length() > 30) { 
1789          p.addText(c.getValue().substring(0, 30)+"..."); 
1790        } else { 
1791          p.addText(c.getValue()); 
1792        } 
1793      } 
1794    } 
1795  } 
1796
1797  protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 
1798    String sys = c.primitiveValue("system");
1799    String value = c.primitiveValue("value");
1800    if (sys.equals("phone")) { 
1801      p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 
1802    } else if (sys.equals("fax")) { 
1803      p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 
1804    } else if (sys.equals("email")) { 
1805      p.ah("mailto:"+value).addText(value); 
1806    } else if (sys.equals("url")) { 
1807      if (value.length() > 30) 
1808        p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 
1809      else 
1810        p.ah(context.prefixLocalHref(value)).addText(value); 
1811    } 
1812  } 
1813  private static String describeSystem(String system) { 
1814    if (system == null) 
1815      return ""; 
1816    switch (system) { 
1817    case "phone": return "ph: "; 
1818    case "fax": return "fax: "; 
1819    default: 
1820      return ""; 
1821    } 
1822  } 
1823
1824  protected String displayQuantity(ResourceWrapper q) { 
1825    if (q == null) {
1826      return "";
1827    }
1828    StringBuilder s = new StringBuilder(); 
1829
1830    s.append(q.has("value") ? q.primitiveValue("value") : "?"); 
1831    if (q.has("unit")) 
1832      s.append(" ").append(q.primitiveValue("unit")); 
1833    else if (q.has("code")) 
1834      s.append(" ").append(q.primitiveValue("code")); 
1835
1836    return s.toString(); 
1837  }   
1838
1839  protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { 
1840    if (q.has("comparator")) 
1841      x.addText(q.primitiveValue("comparator")); 
1842    if (q.has("value")) { 
1843      x.addText(context.getTranslated(q.child("value"))); 
1844    } 
1845    if (q.has("unit")) 
1846      x.tx(" "+context.getTranslated(q.child("unit"))); 
1847    else if (q.has("code") && q.has("system")) { 
1848      // 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 
1849      if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 
1850        x.tx(" "+q.primitiveValue("code")); 
1851      else 
1852        x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 
1853    } 
1854    if (context.isTechnicalMode() && q.has("code")) { 
1855      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"))+"')"); 
1856    }       
1857    checkRenderExtensions(status, x, q);
1858  } 
1859
1860
1861  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 
1862    pieces.add(gen.new Piece(null, displayQuantity(q), null)); 
1863  } 
1864
1865  protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 
1866    pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 
1867  } 
1868
1869  public String displayRange(ResourceWrapper q) { 
1870    if (!q.has("low") && !q.has("high")) 
1871      return "?"; 
1872
1873    StringBuilder b = new StringBuilder(); 
1874
1875    ResourceWrapper lowC = q.child("low");
1876    ResourceWrapper highC = q.child("high");
1877    boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit")))  
1878        || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 
1879    String low = "?"; 
1880    if (q.has("low") && lowC.has("value")) 
1881      low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 
1882    String high = displayQuantity(highC); 
1883    if (high.isEmpty()) 
1884      high = "?"; 
1885    b.append(low).append("\u00A0to\u00A0").append(high); 
1886    return b.toString(); 
1887  } 
1888
1889  protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 
1890    if (q.has("low")) 
1891      x.addText(q.child("low").primitiveValue("value").toString()); 
1892    else 
1893      x.tx("?"); 
1894    x.tx("-"); 
1895    if (q.has("high")) 
1896      x.addText(q.child("high").primitiveValue("value").toString()); 
1897    else 
1898      x.tx("?"); 
1899    if (q.has("low") && q.child("low").has("unit")) 
1900      x.tx(" "+q.child("low").primitiveValue("unit")); 
1901  } 
1902
1903  public String displayPeriod(ResourceWrapper p) { 
1904    String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 
1905    s = s + " --> "; 
1906    return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1907  } 
1908
1909  public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 
1910    x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 
1911    x.tx(" --> "); 
1912    x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 
1913  } 
1914
1915  public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 
1916    renderCoding(status, x, u.child("code"), false); 
1917    x.tx(" = "); 
1918    renderDataType(status, x, u.child("value"));     
1919  } 
1920
1921
1922  public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 
1923    if (x.isPara()) { 
1924      x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1925      x.tx(": "); 
1926      x.tx(td.child("type").primitiveValue("display")); 
1927
1928      if (td.has("name")) {     
1929        x.tx(", "); 
1930        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1931        x.tx(": "); 
1932        x.tx(context.getTranslated(td.child("name"))); 
1933      } 
1934      if (td.has("code")) {     
1935        x.tx(", "); 
1936        x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1937        x.tx(": "); 
1938        renderCodeableConcept(status, x, td.child("code")); 
1939      } 
1940      if (td.has("timing")) {     
1941        x.tx(", "); 
1942        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1943        x.tx(": "); 
1944        renderDataType(status, x, td.child("timing")); 
1945      } 
1946      if (td.has("condition")) {     
1947        x.tx(", "); 
1948        x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1949        x.tx(": "); 
1950        renderExpression(status, x, td.child("condition")); 
1951      }     
1952    } else { 
1953      XhtmlNode tbl = x.table("grid", false); 
1954
1955      XhtmlNode tr = tbl.tr();   
1956      tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1957      tr.td().tx(td.child("type").primitiveValue("display")); 
1958
1959      if (td.has("name")) {     
1960        tr = tbl.tr();   
1961        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 
1962        tr.td().tx(context.getTranslated(td.child("name"))); 
1963      } 
1964      if (td.has("code")) {     
1965        tr = tbl.tr();   
1966        tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 
1967        renderCodeableConcept(status, tr.td(), td.child("code")); 
1968      } 
1969      if (td.has("timing")) {     
1970        tr = tbl.tr();   
1971        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 
1972        renderDataType(status, tr.td(), td.child("timing")); 
1973      } 
1974      if (td.has("condition")) {      
1975        tr = tbl.tr();   
1976        tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 
1977        renderExpression(status, tr.td(), td.child("condition")); 
1978      }     
1979    } 
1980  } 
1981
1982  public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 
1983    XhtmlNode tbl = x.table("grid", false); 
1984    XhtmlNode tr = tbl.tr();     
1985    XhtmlNode td = tr.td().colspan("2"); 
1986    td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 
1987    td.tx(": "); 
1988    StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 
1989    if (sd != null && sd.hasWebPath()) { 
1990      td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 
1991    } else { 
1992      td.tx(dr.primitiveValue("type")); 
1993    } 
1994    if (dr.has("profile")) { 
1995      td.tx(" ("); 
1996      boolean first = true; 
1997      for (ResourceWrapper p : dr.children("profile")) { 
1998        if (first) first = false; else td.tx(" | "); 
1999        sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 
2000        if (sd != null && sd.hasWebPath()) { 
2001          td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 
2002        } else { 
2003          td.tx(p.primitiveValue()); 
2004        } 
2005      } 
2006      td.tx(")"); 
2007    } 
2008    if (dr.has("subject")) { 
2009      tr = tbl.tr();     
2010      td = tr.td().colspan("2"); 
2011      td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 
2012      ResourceWrapper subj = dr.child("subject");
2013      if (subj.fhirType().equals("reference")) { 
2014        renderReference(status, td, subj); 
2015      } else { 
2016        renderCodeableConcept(status, td, subj); 
2017      } 
2018    } 
2019    if (dr.has("codeFilter") || dr.has("dateFilter")) { 
2020      tr = tbl.tr().backgroundColor("#efefef");     
2021      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 
2022      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 
2023    } 
2024    for (ResourceWrapper cf : dr.children("codeFilter")) { 
2025      tr = tbl.tr();     
2026      if (cf.has("path")) { 
2027        tr.td().tx(cf.primitiveValue("path")); 
2028      } else { 
2029        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
2030      } 
2031      if (cf.has("valueSet")) { 
2032        td = tr.td(); 
2033        td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 
2034        renderDataType(status, td, cf.child("valueSet")); 
2035      } else { 
2036        boolean first = true; 
2037        td = tr.td(); 
2038        td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 
2039        for (ResourceWrapper c : cf.children("code")) { 
2040          if (first) first = false; else td.tx(", "); 
2041          renderDataType(status, td, c); 
2042        } 
2043      } 
2044    } 
2045    for (ResourceWrapper cf : dr.children("dateFilter")) { 
2046      tr = tbl.tr();     
2047      if (cf.has("path")) { 
2048        tr.td().tx(cf.primitiveValue("path")); 
2049      } else { 
2050        tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 
2051      } 
2052      renderDataType(status, tr.td(), cf.child("value")); 
2053    } 
2054    if (dr.has("sort") || dr.has("limit")) { 
2055      tr = tbl.tr();     
2056      td = tr.td().colspan("2"); 
2057      if (dr.has("limit")) { 
2058        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 
2059        td.tx(": "); 
2060        td.tx(dr.primitiveValue("limit")); 
2061        if (dr.has("sort")) { 
2062          td.tx(", "); 
2063        } 
2064      } 
2065      if (dr.has("sort")) { 
2066        td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 
2067        td.tx(": "); 
2068        boolean first = true; 
2069        for (ResourceWrapper p : dr.children("sort")) { 
2070          if (first) first = false; else td.tx(" | "); 
2071          td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 
2072          td.tx(p.primitiveValue("path")); 
2073        } 
2074      } 
2075    } 
2076  } 
2077
2078
2079  private String displayTiming(ResourceWrapper s) throws FHIRException { 
2080    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2081    if (s.has("code")) {
2082      b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 
2083    }
2084
2085    if (s.has("event")) { 
2086      CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 
2087      for (ResourceWrapper p : s.children("event")) { 
2088        if (p.hasPrimitiveValue()) { 
2089          c.append(displayDateTime(p)); 
2090        } else if (!renderExpression(c, p)) { 
2091          c.append("??"); 
2092        }         
2093      } 
2094      b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 
2095    } 
2096
2097    if (s.has("repeat")) { 
2098      ResourceWrapper rep = s.child("repeat"); 
2099      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 
2100        b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 
2101      if (rep.has("count")) 
2102        b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 
2103      if (rep.has("duration")) 
2104        b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("duration")))) + " "); 
2105
2106      String st = ""; 
2107      if (rep.has("offset")) { 
2108        st = rep.primitiveValue("offset")+"min "; 
2109      } 
2110      if (!Utilities.noString(st)) {
2111        b.append(st); 
2112      }
2113      for (ResourceWrapper wh : rep.children("when"))  {
2114        b.append(displayEventCode(wh.primitiveValue()));
2115      }
2116      st = ""; 
2117      if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 
2118        st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 
2119      } else { 
2120        st = rep.primitiveValue("frequency"); 
2121        if (rep.has("frequencyMax")) 
2122          st = st + "-"+rep.primitiveValue("frequencyMax"); 
2123      } 
2124      if (rep.has("period")) { 
2125        st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); 
2126        if (rep.has("periodMax")) {
2127          st = st + "-"+rep.primitiveValue("periodMax");
2128        }
2129        st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("period"))); 
2130      } 
2131      if (!Utilities.noString(st)) {
2132        b.append(st);
2133      }
2134      if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) { 
2135        b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " ");
2136      }
2137    } 
2138    return b.toString(); 
2139  } 
2140
2141  private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 
2142    ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 
2143    if (exp == null || !exp.has("value")) { 
2144      return false; 
2145    } 
2146    c.append(exp.child("value").primitiveValue("expression")); 
2147    return true; 
2148  } 
2149
2150  private String displayEventCode(String when) { 
2151    if (when == null) 
2152      return "??"; 
2153    switch (when.toLowerCase()) { 
2154    case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 
2155    case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 
2156    case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 
2157    case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 
2158    case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 
2159    case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 
2160    case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 
2161    case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 
2162    case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 
2163    case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 
2164    case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 
2165    case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 
2166    case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 
2167    case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 
2168    case "morn": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING));  
2169    case "morn.early": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_EARLY)); 
2170    case "morn.late": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_LATE)); 
2171    case "noon": return (context.formatPhrase(RenderingContext.DATA_REND_NOON));   
2172    case "aft": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON));  
2173    case "aft.early": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_EARLY));  
2174    case "aft.late": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_LATE));  
2175    case "eve": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING));   
2176    case "eve.early": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_EARLY)); 
2177    case "eve.late": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_LATE));  
2178    case "night": return (context.formatPhrase(RenderingContext.DATA_REND_NIGHT));   
2179    case "phs": return (context.formatPhrase(RenderingContext.DATA_REND_AFTER_SLEEP)); 
2180    case "imd": return (context.formatPhrase(RenderingContext.DATA_REND_IMMEDIATE));  
2181    
2182    default: return "?"+when+"?"; 
2183    } 
2184  } 
2185
2186  private String displayTimeUnits(String units, boolean singular) { 
2187    if (units == null) 
2188      return "??"; 
2189    switch (units) { 
2190    case "a": return singular ? "year" : "years"; 
2191    case "d": return singular ? "day" : "days"; 
2192    case "h": return singular ? "hour" : "hours"; 
2193    case "min": return singular ? "minute" : "minutes"; 
2194    case "mo": return singular ? "month" : "months"; 
2195    case "s": return singular ? "second" : "seconds"; 
2196    case "wk": return singular ? "week" : "weeks"; 
2197    default: return "?"+units+"?"; 
2198    } 
2199  } 
2200
2201  protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 
2202    x.addText(displayTiming(s)); 
2203  } 
2204
2205
2206  private String displaySampledData(ResourceWrapper s) { 
2207    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 
2208    if (s.has("origin")) 
2209      b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 
2210
2211    if (s.has("interval")) { 
2212      b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 
2213
2214      if (s.has("intervalUnit")) 
2215        b.append(s.primitiveValue("intervalUnit")); 
2216    } 
2217
2218    if (s.has("factor")) 
2219      b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 
2220
2221    if (s.has("lowerLimit")) 
2222      b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 
2223
2224    if (s.has("upperLimit")) 
2225      b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 
2226
2227    if (s.has("dimensions")) 
2228      b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 
2229
2230    if (s.has("data")) 
2231      b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 
2232
2233    return b.toString(); 
2234  } 
2235
2236  protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 
2237    x.addText(displaySampledData(sampledData)); 
2238  } 
2239
2240  public RenderingContext getContext() { 
2241    return context; 
2242  } 
2243
2244
2245  public XhtmlNode makeExceptionXhtml(Exception e, String function) { 
2246    XhtmlNode xn; 
2247    xn = new XhtmlNode(NodeType.Element, "div"); 
2248    XhtmlNode p = xn.para(); 
2249    p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 
2250    p.addComment(getStackTrace(e)); 
2251    return xn; 
2252  } 
2253
2254  private String getStackTrace(Exception e) { 
2255    StringBuilder b = new StringBuilder(); 
2256    b.append("\r\n"); 
2257    for (StackTraceElement t : e.getStackTrace()) { 
2258      b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 
2259      b.append("\r\n"); 
2260    } 
2261    return b.toString(); 
2262  } 
2263
2264  protected String systemFromCanonical(String system) { 
2265    if (system == null) { 
2266      return null; 
2267    } else if (system.contains("|")) { 
2268      return system.substring(0, system.indexOf("|")); 
2269    } else { 
2270      return null; 
2271    } 
2272  } 
2273
2274  protected String versionFromCanonical(String system) { 
2275    if (system == null) { 
2276      return null; 
2277    } else if (system.contains("|")) { 
2278      return system.substring(system.indexOf("|")+1); 
2279    } else { 
2280      return system; 
2281    } 
2282  } 
2283
2284
2285  /**
2286   * when we run into an unknown (canonical) URL, we assume that it's a pointer to something we don't 
2287   * know about, and render it as an 'a href=' in case it is valid. But in the 'known' domains, where 
2288   * we reasonably expect to know everything , we don't make them links 
2289   * @return
2290   */
2291  protected boolean isInKnownUrlSpace(String url) {
2292    return Utilities.startsWithInList(url, 
2293        "http://hl7.org/fhir",  "http://fhir.org/guides",  "http://ihe.net/fhir",  "http://terminology.hl7.org", 
2294        "https://hl7.org/fhir", "https://fhir.org/guides", "https://ihe.net/fhir", "https://terminology.hl7.org", 
2295        "http://www.hl7.org/fhir",  "http://www.fhir.org/guides",  "http://www.ihe.net/fhir",
2296        "https://www.hl7.org/fhir", "https://www.fhir.org/guides", "https://www.ihe.net/fhir"
2297       );
2298  }
2299}