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